From 12a583295d28615dfa5c146055f7b0ecff0d4b5e Mon Sep 17 00:00:00 2001 From: liqi Date: Sat, 7 Jun 2025 15:54:17 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 8 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/nl-utils.iml | 8 + README.md | 36 ++ v1/README.md | 15 + v1/main.py | 327 ++++++++++++ v1/requirements.txt | 0 v1/styles/styles.py | 240 +++++++++ v1/views/app_center.py | 258 ++++++++++ v1/views/history.py | 471 +++++++++++++++++ v1/views/login.py | 482 ++++++++++++++++++ v1/views/nav.py | 282 ++++++++++ v1/views/word_to_pdf.py | 338 ++++++++++++ 15 files changed, 2486 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/nl-utils.iml create mode 100644 README.md create mode 100644 v1/README.md create mode 100644 v1/main.py create mode 100644 v1/requirements.txt create mode 100644 v1/styles/styles.py create mode 100644 v1/views/app_center.py create mode 100644 v1/views/history.py create mode 100644 v1/views/login.py create mode 100644 v1/views/nav.py create mode 100644 v1/views/word_to_pdf.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..db8786c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..d71f456 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/nl-utils.iml b/.idea/nl-utils.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/.idea/nl-utils.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..814ca1c --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# NL-Utils 工具程序 + +## 项目架构 + +``` +l-utils/ +├── main.py # 程序入口 +├── pages/ # 页面模块 +│ ├── login_page.py # 登录页面 +│ ├── main_page.py # 主页面 +│ └── ... # 其他功能页面 +├── services/ # 服务模块 +│ ├── auth_service.py # 认证服务 +│ ├── file_service.py # 文件服务 +│ └── ... # 其他服务 +└── README.md # 项目文档 +``` + +## 功能说明 + +1. **登录系统** + - 默认用户: liqi/qiqi991012 + - 未登录时隐藏侧边栏 + +2. **UI设计** + - 基于PyQt5的暗色主题 + - 响应式布局 + - 默认窗口大小优化 + +3. **文件上传** + - 支持拖拽上传 + - 每行文件可单独删除 + +4. **使用记录** + - 操作记录存储在本地 + - 侧边栏显示历史记录 \ No newline at end of file diff --git a/v1/README.md b/v1/README.md new file mode 100644 index 0000000..84c2b4f --- /dev/null +++ b/v1/README.md @@ -0,0 +1,15 @@ +## 目录结构 +```angular2html +doc_processor/ +│── main.py # 主入口文件 +│── styles/ # 样式文件 +│ └── styles.py # 全局样式 +│── utils/ # 工具类 +│ └── auth.py # 认证相关 +│── views/ # 视图文件 +│ ├── __init__.py +│ ├── login.py # 登录页 +│ ├── nav.py # 导航栏 +│ ├── app_center.py # 应用中心 +│ └── word_to_pdf.py # Word转PDF功能页 +``` \ No newline at end of file diff --git a/v1/main.py b/v1/main.py new file mode 100644 index 0000000..d946a97 --- /dev/null +++ b/v1/main.py @@ -0,0 +1,327 @@ +import sys +from PyQt5.QtWidgets import QApplication, QMainWindow, QStackedWidget, QHBoxLayout, QWidget +from PyQt5.QtCore import Qt +from views.login import LoginPage +from views.nav import NavBar +from views.app_center import AppCenter +from views.word_to_pdf import WordToPDFPage +from views.history import HistoryPage +from PyQt5.QtGui import QPalette, QColor + +# 优化的暗色主题样式表 +STYLES_TXT = """ +/* ===== 全局样式 ===== */ +QWidget { + font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif; + font-size: 14px; + color: #e2e8f0; + border: none; +} + +/* 输入框 */ +QLineEdit { + background-color: #2d3748; + border: 1px solid #4a5568; + border-radius: 6px; + padding: 10px; + color: #e2e8f0; + min-height: 40px; +} + +QLineEdit:focus { + border-color: #818cf8; + outline: none; +} + +/* 按钮通用样式 */ +QPushButton { + background-color: #4f46e5; + color: white; + border-radius: 6px; + padding: 8px 16px; + border: none; + min-height: 35px; + font-weight: 500; + transition: all 0.2s ease; +} + +QPushButton:hover { + background-color: #4338ca; + transform: translateY(-1px); +} + +QPushButton:disabled { + background-color: #4a5568; + color: #a0aec0; +} + +/* 列表控件 */ +QListWidget { + background-color: #2d3748; + border: 1px solid #4a5568; + border-radius: 8px; + padding: 4px; + color: #e2e8f0; + outline: none; +} + +QListWidget::item { + padding: 8px 12px; + border-bottom: 1px solid #4a5568; +} + +QListWidget::item:selected { + background-color: rgba(129, 140, 248, 0.15); + color: #e2e8f0; + border-radius: 4px; +} + +/* 滚动条 */ +QScrollBar:vertical { + background: #2d3748; + width: 10px; + border: none; +} + +QScrollBar::handle:vertical { + background: #4a5568; + min-height: 20px; + border-radius: 4px; +} + +QScrollBar::handle:vertical:hover { + background: #718096; +} + +QScrollBar::add-line:vertical, +QScrollBar::sub-line:vertical { + height: 0; +} + +QScrollBar::add-page:vertical, +QScrollBar::sub-page:vertical { + background: none; +} + +/* 标签 */ +QLabel { + color: #e2e8f0; + background-color: transparent; +} + +/* 菜单栏 */ +QMenuBar { + background-color: #1a202c; + color: #e2e8f0; + border-bottom: 1px solid #2d3748; +} + +QMenuBar::item { + background-color: transparent; + padding: 4px 10px; +} + +QMenuBar::item:selected { + background-color: #4f46e5; + color: white; +} + +QMenu { + background-color: #2d3748; + border: 1px solid #4a5568; + color: #e2e8f0; + padding: 4px; +} + +QMenu::item { + padding: 6px 30px 6px 20px; +} + +QMenu::item:selected { + background-color: #4f46e5; + color: white; +} + +QMenu::separator { + height: 1px; + background: #4a5568; + margin: 4px 8px; +} + +/* 进度条 */ +QProgressBar { + border-radius: 4px; + height: 8px; + background-color: #2d3748; + border: 1px solid #4a5568; + text-align: center; +} + +QProgressBar::chunk { + background-color: #4f46e5; + border-radius: 4px; +} + +/* 复选框 */ +QCheckBox { + color: #e2e8f0; + spacing: 8px; +} + +QCheckBox::indicator { + width: 16px; + height: 16px; + border: 1px solid #4a5568; + border-radius: 3px; + background-color: #2d3748; +} + +QCheckBox::indicator:checked { + background-color: #4f46e5; + border: 1px solid #4f46e5; +} + +/* 分组框 */ +QGroupBox { + font-weight: bold; + background-color: #2d3748; + border: 1px solid #4a5568; + border-radius: 8px; + margin-top: 1.5ex; + padding-top: 10px; +} + +QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + background-color: transparent; + padding: 0 3px; + left: 9px; +} +""" + + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + self.username = None + self.init_ui() + + def init_ui(self): + self.setWindowTitle("文档处理工具") + self.setGeometry(100, 100, 1200, 800) + self.setStyleSheet(STYLES_TXT) + + # 设置暗色主题调色板 + palette = QPalette() + palette.setColor(QPalette.Window, QColor(26, 32, 44)) # #1a202c + palette.setColor(QPalette.WindowText, QColor(226, 232, 240)) # #e2e8f0 + palette.setColor(QPalette.Base, QColor(45, 55, 72)) # #2d3748 + palette.setColor(QPalette.AlternateBase, QColor(42, 50, 65)) # #2a3241 + palette.setColor(QPalette.ToolTipBase, QColor(26, 32, 44)) # #1a202c + palette.setColor(QPalette.ToolTipText, QColor(226, 232, 240)) # #e2e8f0 + palette.setColor(QPalette.Text, QColor(226, 232, 240)) # #e2e8f0 + palette.setColor(QPalette.Button, QColor(79, 70, 229)) # #4f46e5 + palette.setColor(QPalette.ButtonText, QColor(226, 232, 240)) # #e2e8f0 + palette.setColor(QPalette.BrightText, Qt.white) + palette.setColor(QPalette.Link, QColor(129, 140, 248)) # #818cf8 + palette.setColor(QPalette.Highlight, QColor(129, 140, 248)) # #818cf8 + palette.setColor(QPalette.HighlightedText, Qt.white) + palette.setColor(QPalette.Disabled, QPalette.Text, QColor(160, 174, 192)) # #a0aec0 + self.setPalette(palette) + + # 主窗口部件 + self.main_widget = QWidget() + self.main_layout = QHBoxLayout(self.main_widget) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.setSpacing(0) + + # 导航栏 (初始隐藏) + self.nav_bar = NavBar("") + self.nav_bar.setObjectName("navBar") + self.nav_bar.hide() + self.nav_bar.nav_item_clicked.connect(self.handle_nav_click) + self.nav_bar.logout_clicked.connect(self.handle_logout) + + # 页面堆栈 + self.stacked_pages = QStackedWidget() + self.stacked_pages.setObjectName("stackedPages") + + # 登录页 + self.login_page = LoginPage() + self.login_page.login_success.connect(self.handle_login_success) + + # 应用中心页 + self.app_center = AppCenter() + self.app_center.app_selected.connect(self.handle_app_selected) + + # Word转PDF页 + self.word_to_pdf = WordToPDFPage() + + # 使用记录页 + self.history_page = HistoryPage() + + # 设置页(待实现) + self.settings_page = QWidget() + self.settings_page.setObjectName("settingsPage") + + # 添加页面 + self.stacked_pages.addWidget(self.login_page) + self.stacked_pages.addWidget(self.app_center) + self.stacked_pages.addWidget(self.word_to_pdf) + self.stacked_pages.addWidget(self.history_page) + self.stacked_pages.addWidget(self.settings_page) + + # 主布局 + self.main_layout.addWidget(self.nav_bar) + self.main_layout.addWidget(self.stacked_pages, 1) # 设置弹性因子为1 + + self.setCentralWidget(self.main_widget) + + def handle_login_success(self, username): + self.username = username + # 更新导航栏用户信息 + self.nav_bar.username = username + self.nav_bar.show() + # 设置默认激活页 + self.nav_bar.set_active_nav("home") + self.stacked_pages.setCurrentWidget(self.app_center) + + def handle_logout(self): + self.username = None + self.nav_bar.hide() + self.stacked_pages.setCurrentWidget(self.login_page) + + def handle_nav_click(self, nav_id): + # 确保导航栏状态更新 + self.nav_bar.set_active_nav(nav_id) + + # 根据导航ID切换页面 + if nav_id == "home": + self.stacked_pages.setCurrentWidget(self.app_center) + elif nav_id == "history": + self.stacked_pages.setCurrentWidget(self.history_page) + elif nav_id == "settings": + 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) + # 后续可添加其他应用 + + +def main(): + app = QApplication(sys.argv) + app.setStyle('Fusion') + + # 设置高DPI支持 + app.setAttribute(Qt.AA_EnableHighDpiScaling) + app.setAttribute(Qt.AA_UseHighDpiPixmaps) + + window = MainWindow() + window.show() + sys.exit(app.exec_()) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/v1/requirements.txt b/v1/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/v1/styles/styles.py b/v1/styles/styles.py new file mode 100644 index 0000000..12130cb --- /dev/null +++ b/v1/styles/styles.py @@ -0,0 +1,240 @@ +styles_txt = """ +/* 全局样式 */ +QWidget { + font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif; + font-size: 14px; + color: #e2e8f0; +} + +/* 登录页面 */ +#loginPage { + background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #667eea, stop:1 #764ba2); +} + +#loginCard { + background-color: rgba(255, 255, 255, 0.9); + border-radius: 12px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); +} + +#loginTitle { + font-size: 28px; + font-weight: bold; + color: #2d3748; +} + +#formLabel { + font-size: 14px; + font-weight: 600; + color: #4a5568; +} + +#formInput { + background-color: #edf2f7; + border: 1px solid #e2e8f0; + border-radius: 6px; + padding: 10px; + color: #2d3748; +} + +#formInput:focus { + border-color: #667eea; +} + +#loginBtn { + background-color: #4f46e5; + color: white; + border-radius: 6px; + padding: 12px; + font-weight: bold; +} + +#loginBtn:hover { + background-color: #4338ca; +} + +/* 导航栏 */ +#navBar { + background-color: #1a202c; +} + +#userFrame { + background-color: #2d3748; + border-radius: 8px; +} + +#userName { + font-size: 16px; + font-weight: bold; + color: white; +} + +#navBtn { + text-align: left; + padding: 12px 20px; + border-radius: 6px; + color: #a0aec0; +} + +#navBtn:hover { + background-color: #2d3748; + color: white; +} + +#logoutBtn { + text-align: left; + padding: 12px 20px; + border-radius: 6px; + color: #e53e3e; +} + +#logoutBtn:hover { + background-color: #2d3748; + color: #f56565; +} + +/* 应用中心 */ +#appCenter { + background-color: #1a202c; +} + +#pageTitle { + font-size: 24px; + font-weight: bold; + color: white; +} + +#appCard { + border-radius: 12px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + transition: transform 0.2s; +} + +#appCard:hover { + transform: translateY(-5px); +} + +/* Word转PDF页面 */ +#wordToPdfPage { + background-color: #1a202c; +} + +#leftPanel, #rightPanel { + background-color: #2d3748; + border-radius: 12px; + padding: 20px; +} + +#uploadArea { + border: 2px dashed #4a5568; + border-radius: 8px; + background-color: rgba(74, 85, 104, 0.2); +} + +#uploadArea:hover { + border-color: #667eea; + background-color: rgba(102, 126, 234, 0.1); +} + +#uploadBtn { + background-color: #4f46e5; + color: white; + border-radius: 6px; + padding: 8px 16px; +} + +#uploadBtn:hover { + background-color: #4338ca; +} + +#fileList, #resultList, #logContent { + background-color: #1a202c; + border: 1px solid #4a5568; + border-radius: 8px; + padding: 8px; +} + +#convertBtn { + background-color: #4CAF50; + color: white; + border-radius: 6px; + padding: 8px 16px; +} + +#convertBtn:hover { + background-color: #3d8b40; +} + +#deleteBtn { + background-color: #f44336; + color: white; + border-radius: 6px; + padding: 8px 16px; +} + +#deleteBtn:hover { + background-color: #d32f2f; +} + +#downloadBtn { + background-color: #2196F3; + color: white; + border-radius: 6px; + padding: 8px 16px; +} + +#downloadBtn:hover { + background-color: #1976d2; +} + +#logArea { + background-color: #1a202c; + border-radius: 8px; + padding: 12px; +} + +#logTitle { + font-size: 16px; + font-weight: bold; + color: white; + padding-bottom: 8px; + border-bottom: 1px solid #4a5568; +} + +QProgressBar { + border-radius: 4px; + height: 8px; + background-color: #4a5568; +} + +QProgressBar::chunk { + background-color: #4f46e5; + border-radius: 4px; +} + +/* 滚动条 */ +QScrollBar:vertical { + border: none; + background: #2d3748; + width: 10px; + margin: 0; +} + +QScrollBar::handle:vertical { + background: #4a5568; + min-height: 20px; + border-radius: 4px; +} + +QScrollBar::handle:vertical:hover { + background: #718096; +} + +QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { + height: 0; +} + +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; +} +""" \ No newline at end of file diff --git a/v1/views/app_center.py b/v1/views/app_center.py new file mode 100644 index 0000000..f7a2f0e --- /dev/null +++ b/v1/views/app_center.py @@ -0,0 +1,258 @@ +import math + +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 + + +class GradientFrame(QFrame): + def __init__(self, start_color, end_color, angle=0, parent=None): + super().__init__(parent) + self.start_color = QColor(start_color) + self.end_color = QColor(end_color) + self.angle = angle # 0-360 degrees + + def paintEvent(self, event): + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + + 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) + 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.setColorAt(0, self.start_color) + gradient.setColorAt(1, self.end_color) + + painter.fillRect(self.rect(), QBrush(gradient)) + + super().paintEvent(event) + + +class AppCenter(QWidget): + app_selected = pyqtSignal(str) + + def __init__(self): + super().__init__() + self.init_ui() + + def init_ui(self): + self.setObjectName("appCenter") + self.setStyleSheet(""" + #appCenter { + background-color: #1a202c; + } + + #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() + + title = QLabel("应用中心") + title.setObjectName("pageTitle") + + title_spacer = QSpacerItem(40, 20) + + title_layout.addWidget(title) + title_layout.addSpacerItem(title_spacer) + + layout.addLayout(title_layout) + + # 卡片容器 + grid_layout = QVBoxLayout() + grid_layout.setSpacing(20) + + # 应用卡片 + 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") + } + ] + + # 每行显示2张卡片 + for i in range(0, len(apps), 2): + row_layout = QHBoxLayout() + row_layout.setSpacing(30) + + 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) + + def create_app_card(self, app): + """创建应用卡片""" + card = GradientFrame(app["color"][0], app["color"][1], 45) + card.setObjectName("appCard") + card.setFixedSize(300, 280) + + layout = QVBoxLayout(card) + layout.setContentsMargins(20, 20, 20, 20) + 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.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) + # 转换图标为白色 + if app["id"] != "coming_soon": + 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) + + # 添加伸缩空间保持底部对齐 + spacer = QSpacerItem(20, 20) + layout.addItem(spacer) + + # 按钮 + 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(255, 255, 255, 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 \ No newline at end of file diff --git a/v1/views/history.py b/v1/views/history.py new file mode 100644 index 0000000..122a4ad --- /dev/null +++ b/v1/views/history.py @@ -0,0 +1,471 @@ +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QTableWidget, QTableWidgetItem, + QHeaderView, QAbstractItemView, QFrame, + QComboBox, QDateEdit, QLineEdit, QSpacerItem) +from PyQt5.QtCore import Qt, QDate +from PyQt5.QtGui import QBrush, QColor, QIcon, QFont + + +class HistoryPage(QWidget): + def __init__(self): + super().__init__() + self.setObjectName("historyPage") + self.init_ui() + + def init_ui(self): + main_layout = QVBoxLayout() + main_layout.setContentsMargins(25, 20, 25, 20) + main_layout.setSpacing(15) + + # 标题区域 + title_layout = QHBoxLayout() + + title = QLabel("使用记录") + title.setObjectName("pageTitle") + title.setFont(QFont("Segoe UI", 18, QFont.Bold)) + title_layout.addWidget(title) + + title_layout.addStretch() + + # 导出按钮 + export_btn = QPushButton("导出记录") + export_btn.setObjectName("exportBtn") + export_btn.setIcon(QIcon(":/icons/download.png")) + export_btn.setCursor(Qt.PointingHandCursor) + title_layout.addWidget(export_btn) + + # 刷新按钮 + refresh_btn = QPushButton("刷新") + refresh_btn.setObjectName("refreshBtn") + refresh_btn.setIcon(QIcon(":/icons/refresh.png")) + refresh_btn.setCursor(Qt.PointingHandCursor) + title_layout.addWidget(refresh_btn) + + main_layout.addLayout(title_layout) + + # 筛选控件 + filter_layout = QHBoxLayout() + filter_layout.setContentsMargins(0, 0, 0, 0) + filter_layout.setSpacing(10) + + # 类型选择器 + type_combo = QComboBox() + type_combo.addItems(["全部操作", "转换", "下载", "上传", "删除"]) + type_combo.setFixedWidth(120) + type_combo.setObjectName("filterCombo") + + # 日期范围选择 + date_layout = QHBoxLayout() + date_layout.setSpacing(5) + + start_date = QDateEdit() + start_date.setDate(QDate.currentDate().addMonths(-1)) + start_date.setCalendarPopup(True) + start_date.setDisplayFormat("yyyy-MM-dd") + start_date.setFixedWidth(110) + start_date.setObjectName("dateInput") + + to_label = QLabel("至") + + end_date = QDateEdit() + end_date.setDate(QDate.currentDate()) + end_date.setCalendarPopup(True) + end_date.setDisplayFormat("yyyy-MM-dd") + end_date.setFixedWidth(110) + end_date.setObjectName("dateInput") + + date_layout.addWidget(start_date) + date_layout.addWidget(to_label) + date_layout.addWidget(end_date) + + # 搜索框 + search_layout = QHBoxLayout() + search_layout.setSpacing(5) + search_layout.setContentsMargins(0, 0, 0, 0) + + search_input = QLineEdit() + search_input.setPlaceholderText("搜索文件名、操作类型...") + search_input.setFixedWidth(250) + search_input.setObjectName("searchInput") + + search_btn = QPushButton() + search_btn.setIcon(QIcon(":/icons/search.png")) + search_btn.setObjectName("searchBtn") + search_btn.setFixedSize(40, 40) + search_btn.setCursor(Qt.PointingHandCursor) + + search_layout.addWidget(search_input) + search_layout.addWidget(search_btn) + + # 添加到主筛选布局 + filter_layout.addWidget(type_combo) + filter_layout.addSpacing(15) + filter_layout.addWidget(QLabel("时间段:")) + filter_layout.addLayout(date_layout) + filter_layout.addSpacing(15) + filter_layout.addWidget(QLabel("搜索:")) + filter_layout.addLayout(search_layout) + filter_layout.addStretch() + + main_layout.addLayout(filter_layout) + + # 结果计数 + count_layout = QHBoxLayout() + count_layout.setContentsMargins(0, 0, 0, 0) + + result_count = QLabel("共找到 125 条记录") + result_count.setObjectName("resultCount") + result_count.setStyleSheet("color: #a0aec0; font-size: 13px;") + count_layout.addWidget(result_count) + count_layout.addStretch() + + main_layout.addLayout(count_layout) + + # 表格区域 + self.table = QTableWidget(0, 5) # 0行,5列 + self.table.setObjectName("historyTable") + self.table.setHorizontalHeaderLabels(["日期时间", "操作类型", "文件名称", "状态", ""]) + self.table.setColumnWidth(0, 160) + self.table.setColumnWidth(1, 100) + self.table.setColumnWidth(2, 280) + self.table.setColumnWidth(3, 80) + self.table.setColumnWidth(4, 100) + self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) + self.table.verticalHeader().setVisible(False) + self.table.setEditTriggers(QAbstractItemView.NoEditTriggers) + self.table.setSelectionBehavior(QAbstractItemView.SelectRows) + self.table.setAlternatingRowColors(True) + + # 表头样式 + header = self.table.horizontalHeader() + header.setStyleSheet(""" + QHeaderView::section { + background-color: #2d3748; + padding: 12px 15px; + border: none; + color: #a0aec0; + font-weight: 600; + font-size: 13px; + } + """) + + # 填充示例数据 + self.populate_table() + + main_layout.addWidget(self.table, 1) # 弹性因子1 + + # 分页控件 + pagination_layout = QHBoxLayout() + pagination_layout.setContentsMargins(0, 10, 0, 0) + + # 每页显示选择 + per_page_layout = QHBoxLayout() + per_page_layout.setSpacing(5) + per_page_layout.addWidget(QLabel("每页显示:")) + + per_page_combo = QComboBox() + per_page_combo.addItems(["10", "25", "50", "100"]) + per_page_combo.setCurrentText("25") + per_page_combo.setFixedWidth(80) + per_page_combo.setObjectName("pageCombo") + per_page_layout.addWidget(per_page_combo) + + pagination_layout.addLayout(per_page_layout) + pagination_layout.addStretch() + + # 分页导航 + prev_btn = QPushButton("上一页") + prev_btn.setObjectName("paginationBtn") + prev_btn.setCursor(Qt.PointingHandCursor) + prev_btn.setFixedWidth(80) + prev_btn.setEnabled(False) + + page_label = QLabel("第 1 / 5 页") + page_label.setObjectName("pageLabel") + page_label.setFixedWidth(100) + page_label.setAlignment(Qt.AlignCenter) + + next_btn = QPushButton("下一页") + next_btn.setObjectName("paginationBtn") + next_btn.setCursor(Qt.PointingHandCursor) + next_btn.setFixedWidth(80) + + pagination_layout.addWidget(prev_btn) + pagination_layout.addWidget(page_label) + pagination_layout.addWidget(next_btn) + + main_layout.addLayout(pagination_layout) + + self.setLayout(main_layout) + + def populate_table(self): + # 示例数据 + records = [ + {"time": "2023-06-15 14:30:22", "type": "转换", "file": "季度报告.docx", "status": "成功"}, + {"time": "2023-06-14 10:15:45", "type": "下载", "file": "项目文档.pdf", "status": "成功"}, + {"time": "2023-06-13 16:45:10", "type": "转换", "file": "个人简历.docx", "status": "失败", + "error": "文件格式不支持"}, + {"time": "2023-06-12 09:20:33", "type": "上传", "file": "产品设计图.png", "status": "成功"}, + {"time": "2023-06-11 17:30:18", "type": "删除", "file": "旧项目备份.zip", "status": "成功"}, + {"time": "2023-06-10 08:45:27", "type": "转换", "file": "年度计划.docx", "status": "成功"}, + {"time": "2023-06-09 11:12:03", "type": "下载", "file": "合同范本.pdf", "status": "成功"}, + {"time": "2023-06-08 14:22:55", "type": "转换", "file": "用户手册.docx", "status": "失败", + "error": "文件已损坏"}, + {"time": "2023-06-07 16:30:41", "type": "上传", "file": "会议记录.txt", "status": "成功"}, + {"time": "2023-06-06 10:45:19", "type": "转换", "file": "财务数据.xlsx", "status": "失败", + "error": "不支持Excel转换"}, + ] + + self.table.setRowCount(len(records)) + + for row, record in enumerate(records): + # 时间 + time_item = QTableWidgetItem(record["time"]) + time_item.setFont(QFont("Segoe UI", 10)) + self.table.setItem(row, 0, time_item) + + # 类型 + type_item = QTableWidgetItem(record["type"]) + type_item.setFont(QFont("Segoe UI", 10, QFont.Medium)) + type_item.setTextAlignment(Qt.AlignCenter) + + # 设置类型颜色 + type_colors = { + "转换": "#818cf8", # 紫色 + "下载": "#34d399", # 绿色 + "上传": "#60a5fa", # 蓝色 + "删除": "#f87171", # 红色 + } + color = type_colors.get(record["type"], "#a0aec0") + type_item.setForeground(QBrush(QColor(color))) + self.table.setItem(row, 1, type_item) + + # 文件名 + file_item = QTableWidgetItem(record["file"]) + file_item.setFont(QFont("Segoe UI", 10)) + self.table.setItem(row, 2, file_item) + + # 状态 + status_item = QTableWidgetItem(record["status"]) + status_item.setFont(QFont("Segoe UI", 10, QFont.Medium)) + status_item.setTextAlignment(Qt.AlignCenter) + + # 设置状态颜色 + if record["status"] == "成功": + status_color = QColor("#10b981") # 绿色 + else: + status_color = QColor("#ef4444") # 红色 + + status_item.setForeground(QBrush(status_color)) + self.table.setItem(row, 3, status_item) + + # 操作按钮 + btn_layout = QHBoxLayout() + btn_layout.setContentsMargins(0, 0, 0, 0) + btn_layout.setSpacing(5) + + # 查看按钮 + view_btn = QPushButton("详情") + view_btn.setObjectName("actionBtn") + view_btn.setFixedSize(60, 28) + view_btn.setProperty("row", row) + + # 如果是失败状态,添加重试按钮 + if record.get("error"): + retry_btn = QPushButton("重试") + retry_btn.setObjectName("retryBtn") + retry_btn.setFixedSize(60, 28) + retry_btn.setProperty("row", row) + btn_layout.addWidget(retry_btn) + + # 删除按钮 + delete_btn = QPushButton("删除") + delete_btn.setObjectName("deleteBtn") + delete_btn.setFixedSize(60, 28) + delete_btn.setProperty("row", row) + + btn_layout.addWidget(view_btn) + btn_layout.addWidget(delete_btn) + + # 添加到表格单元格 + widget = QWidget() + widget.setLayout(btn_layout) + self.table.setCellWidget(row, 4, widget) + + # 设置行高 + for row in range(len(records)): + self.table.setRowHeight(row, 45) + + # 设置表格为空时的提示 + if len(records) == 0: + self.table.setRowCount(1) + self.table.setSpan(0, 0, 1, self.table.columnCount()) + + empty_label = QLabel("没有找到记录") + empty_label.setAlignment(Qt.AlignCenter) + empty_label.setStyleSheet("color: #a0aec0; font-size: 16px;") + self.table.setCellWidget(0, 0, empty_label) + self.table.setRowHeight(0, 200) + + # 统一设置按钮样式 + self.set_styles() + + def set_styles(self): + self.setStyleSheet(""" + /* ---------- 页面容器 ---------- */ + QWidget#historyPage { + background-color: #1a202c; + } + + /* ---------- 按钮通用样式 ---------- */ + QPushButton { + border-radius: 4px; + padding: 8px 12px; + font-weight: 500; + transition: all 0.2s; + border: none; + } + + /* 刷新和导出按钮 */ + QPushButton#refreshBtn, + QPushButton#exportBtn { + background-color: #4f46e5; + color: white; + min-height: 40px; + padding: 10px 16px; + min-width: 120px; + } + + QPushButton#refreshBtn:hover, + QPushButton#exportBtn:hover { + background-color: #4338ca; + } + + /* 筛选控件 */ + QComboBox#filterCombo, + QComboBox#pageCombo { + background-color: #2d3748; + border: 1px solid #4a5568; + border-radius: 4px; + padding: 8px 12px; + color: #e2e8f0; + min-height: 35px; + } + + QComboBox#filterCombo::drop-down, + QComboBox#pageCombo::drop-down { + width: 20px; + border: none; + } + + QComboBox#filterCombo QAbstractItemView, + QComboBox#pageCombo QAbstractItemView { + background-color: #2d3748; + border: 1px solid #4a5568; + color: #e2e8f0; + selection-background-color: #4f46e5; + } + + /* 日期输入 */ + QDateEdit#dateInput { + background-color: #2d3748; + border: 1px solid #4a5568; + border-radius: 4px; + padding: 8px 12px; + color: #e2e8f0; + min-height: 35px; + } + + QDateEdit#dateInput::drop-down { + width: 20px; + border: none; + } + + /* 搜索框 */ + QLineEdit#searchInput { + background-color: #2d3748; + border: 1px solid #4a5568; + border-radius: 4px; + padding: 8px 12px; + color: #e2e8f0; + min-height: 35px; + } + + QPushButton#searchBtn { + background-color: #4f46e5; + border-radius: 4px; + } + + QPushButton#searchBtn:hover { + background-color: #4338ca; + } + + /* 分页按钮 */ + QPushButton#paginationBtn { + background-color: #2d3748; + border: 1px solid #4a5568; + color: #e2e8f0; + padding: 8px; + min-height: 35px; + } + + QPushButton#paginationBtn:hover { + background-color: rgba(79, 70, 229, 0.15); + border-color: #818cf8; + } + + /* ---------- 表格样式 ---------- */ + QTableView#historyTable { + background-color: #1a202c; + border: none; + border-radius: 8px; + gridline-color: #2d3748; + selection-background-color: rgba(79, 70, 229, 0.15); + selection-color: white; + alternate-background-color: #19202c; + } + + QTableView#historyTable QTableCornerButton::section { + background-color: #2d3748; + border: none; + } + + QTableView#historyTable::item { + padding: 12px 15px; + border-bottom: 1px solid rgba(74, 85, 104, 0.5); + color: #e2e8f0; + } + + /* 操作按钮 */ + QPushButton#actionBtn { + background-color: rgba(129, 140, 248, 0.1); + color: #818cf8; + border: 1px solid rgba(129, 140, 248, 0.2); + } + + 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); + } + + 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); + } + + QPushButton#deleteBtn:hover { + background-color: rgba(120, 113, 108, 0.2); + } + """) \ No newline at end of file diff --git a/v1/views/login.py b/v1/views/login.py new file mode 100644 index 0000000..e8f2720 --- /dev/null +++ b/v1/views/login.py @@ -0,0 +1,482 @@ +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.QtGui import QPixmap, QPainter, QLinearGradient, QColor, QIcon, QFont, QBrush + + +class LoginPage(QWidget): + login_success = pyqtSignal(str) + + def __init__(self): + super().__init__() + self.init_ui() + self.setup_animations() + + def init_ui(self): + self.setObjectName("loginPage") + self.setMinimumSize(800, 600) + + # 主要布局 + main_layout = QHBoxLayout() + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + self.setLayout(main_layout) + + # 左侧装饰区 - 添加玻璃态效果 + left_frame = QFrame() + left_frame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + left_frame.setObjectName("leftDecor") + + # 右侧登录区 - 深色背景 + right_frame = QFrame() + right_frame.setFixedWidth(400) + right_frame.setObjectName("rightFrame") + right_frame.setStyleSheet(""" + #rightFrame { + background-color: rgba(45, 55, 72, 0.85); + border-left: 1px solid rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + } + """) + + main_layout.addWidget(left_frame, 3) + main_layout.addWidget(right_frame, 1) + + # 右侧登录区布局 + login_layout = QVBoxLayout(right_frame) + login_layout.setContentsMargins(40, 80, 40, 40) + login_layout.setSpacing(0) + + # 标题 - 浅色文字 + title_layout = QVBoxLayout() + title_layout.setSpacing(10) + + welcome_label = QLabel("欢迎回来") + welcome_label.setObjectName("welcomeLabel") + welcome_label.setAlignment(Qt.AlignCenter) + + logo_label = QLabel() + logo_pixmap = QPixmap(":/icons/app_icon.png").scaled(80, 80, Qt.KeepAspectRatio, Qt.SmoothTransformation) + + # 使图标变为白色以适应深色背景 + painter = QPainter(logo_pixmap) + painter.setCompositionMode(QPainter.CompositionMode_SourceIn) + painter.fillRect(logo_pixmap.rect(), Qt.white) + painter.end() + + logo_label.setPixmap(logo_pixmap) + logo_label.setAlignment(Qt.AlignCenter) + + title_label = QLabel("文档处理中心") + title_label.setObjectName("titleLabel") + title_label.setAlignment(Qt.AlignCenter) + + title_layout.addWidget(welcome_label) + title_layout.addWidget(logo_label) + title_layout.addWidget(title_label) + + # 表单区 + form_frame = QFrame() + form_frame.setObjectName("formFrame") + + form_layout = QVBoxLayout(form_frame) + form_layout.setContentsMargins(0, 40, 0, 0) + form_layout.setSpacing(25) + + # 用户名输入框 + username_frame = QFrame() + username_frame.setObjectName("inputFrame") + username_layout = QHBoxLayout(username_frame) + username_layout.setContentsMargins(15, 0, 15, 0) + username_layout.setSpacing(10) + + username_icon = QLabel() + username_pixmap = QPixmap(":/icons/user.png").scaled(24, 24, Qt.KeepAspectRatio, Qt.SmoothTransformation) + + # 使图标变为白色 + painter = QPainter(username_pixmap) + painter.setCompositionMode(QPainter.CompositionMode_SourceIn) + painter.fillRect(username_pixmap.rect(), Qt.white) + painter.end() + username_icon.setPixmap(username_pixmap) + + self.username_input = QLineEdit() + self.username_input.setObjectName("usernameInput") + self.username_input.setPlaceholderText("请输入用户名") + self.username_input.setStyleSheet(""" + #usernameInput { + border: none; + background: transparent; + font-size: 15px; + color: #e2e8f0; + padding: 12px 0; + } + #usernameInput:focus { + outline: none; + } + """) + self.username_input.setAttribute(Qt.WA_MacShowFocusRect, 0) + + username_layout.addWidget(username_icon) + username_layout.addWidget(self.username_input) + + # 密码输入框 + password_frame = QFrame() + password_frame.setObjectName("inputFrame") + password_layout = QHBoxLayout(password_frame) + password_layout.setContentsMargins(15, 0, 15, 0) + password_layout.setSpacing(10) + + password_icon = QLabel() + password_pixmap = QPixmap(":/icons/lock.png").scaled(24, 24, Qt.KeepAspectRatio, Qt.SmoothTransformation) + + # 使图标变为白色 + painter = QPainter(password_pixmap) + painter.setCompositionMode(QPainter.CompositionMode_SourceIn) + painter.fillRect(password_pixmap.rect(), Qt.white) + painter.end() + password_icon.setPixmap(password_pixmap) + + self.password_input = QLineEdit() + self.password_input.setObjectName("passwordInput") + self.password_input.setPlaceholderText("请输入密码") + self.password_input.setEchoMode(QLineEdit.Password) + self.password_input.setStyleSheet(""" + #passwordInput { + border: none; + background: transparent; + font-size: 15px; + color: #e2e8f0; + padding: 12px 0; + } + #passwordInput:focus { + outline: none; + } + """) + self.password_input.setAttribute(Qt.WA_MacShowFocusRect, 0) + + # 显示/隐藏密码按钮 + self.toggle_password_btn = QPushButton() + self.toggle_password_btn.setObjectName("togglePasswordBtn") + + # 加载图标并设置为白色 + eye_off_pixmap = QPixmap(":/icons/eye_off.png").scaled(24, 24, Qt.KeepAspectRatio, Qt.SmoothTransformation) + painter = QPainter(eye_off_pixmap) + painter.setCompositionMode(QPainter.CompositionMode_SourceIn) + painter.fillRect(eye_off_pixmap.rect(), Qt.white) + painter.end() + + self.toggle_password_btn.setIcon(QIcon(eye_off_pixmap)) + self.toggle_password_btn.setIconSize(QSize(18, 18)) + self.toggle_password_btn.setStyleSheet("background: transparent; border: none;") + self.toggle_password_btn.clicked.connect(self.toggle_password_visibility) + + password_layout.addWidget(password_icon) + password_layout.addWidget(self.password_input) + password_layout.addWidget(self.toggle_password_btn) + + # 记住我选项 + remember_layout = QHBoxLayout() + remember_layout.setContentsMargins(15, 0, 0, 0) + + self.remember_check = QCheckBox("记住我") + self.remember_check.setObjectName("rememberCheck") + self.remember_check.setStyleSheet(""" + #rememberCheck { + color: #cbd5e0; + font-size: 13px; + } + """) + + forget_btn = QPushButton("忘记密码?") + forget_btn.setObjectName("forgetPassword") + forget_btn.setStyleSheet(""" + #forgetPassword { + color: #a0aec0; + font-size: 13px; + background: transparent; + border: none; + text-decoration: none; + } + #forgetPassword:hover { + color: #818cf8; + text-decoration: underline; + } + """) + + remember_layout.addWidget(self.remember_check) + remember_layout.addStretch() + remember_layout.addWidget(forget_btn) + + # 错误提示 + self.error_label = QLabel() + self.error_label.setObjectName("errorLabel") + self.error_label.setStyleSheet(""" + #errorLabel { + color: #fc8181; + font-size: 13px; + padding-left: 15px; + min-height: 20px; + } + """) + self.error_label.hide() + + # 登录按钮 + login_btn = QPushButton("登录") + login_btn.setObjectName("loginBtn") + login_btn.clicked.connect(self.handle_login) + + # 添加表单元素 + form_layout.addWidget(username_frame) + form_layout.addWidget(password_frame) + form_layout.addLayout(remember_layout) + form_layout.addWidget(self.error_label) + form_layout.addWidget(login_btn, 0, Qt.AlignCenter) + + # 底部区域 + footer_layout = QVBoxLayout() + footer_layout.setContentsMargins(0, 30, 0, 0) + footer_layout.setSpacing(10) + + signup_layout = QHBoxLayout() + signup_layout.setAlignment(Qt.AlignCenter) + + signup_label = QLabel("没有账户?") + signup_label.setStyleSheet("color: #a0aec0; font-size: 13px;") + + signup_btn = QPushButton("立即注册") + signup_btn.setObjectName("signupBtn") + signup_btn.setStyleSheet(""" + #signupBtn { + background: transparent; + border: none; + color: #818cf8; + font-weight: 600; + font-size: 13px; + padding: 0 5px; + } + #signupBtn:hover { + text-decoration: underline; + } + """) + + signup_layout.addWidget(signup_label) + signup_layout.addWidget(signup_btn) + + footer_layout.addLayout(signup_layout, Qt.AlignCenter) + + # 添加所有组件到主布局 + login_layout.addLayout(title_layout) + login_layout.addWidget(form_frame) + login_layout.addLayout(footer_layout) + + # 应用样式 + self.apply_styles() + + def apply_styles(self): + # 设置全局样式 - 暗色主题 + self.setStyleSheet(""" + /* 登录页面背景渐变 - 深色 */ + #loginPage { + background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 #1a202c, stop:1 #2d3748); + } + + /* 左侧装饰区玻璃效果 - 深色 */ + #leftDecor { + background: rgba(45, 55, 72, 0.15); + border-radius: 0px; + backdrop-filter: blur(15px); + border: 1px solid rgba(255, 255, 255, 0.05); + border-left: none; + } + + /* 欢迎文本 - 浅色 */ + #welcomeLabel { + font-size: 22px; + font-weight: 300; + color: #e2e8f0; + letter-spacing: 2px; + } + + /* 主标题 - 浅色 */ + #titleLabel { + font-size: 28px; + font-weight: bold; + color: #ffffff; + margin-bottom: 15px; + letter-spacing: 1px; + } + + /* 输入框容器 - 深色 */ + #inputFrame { + background: rgba(30, 41, 59, 0.5); + border: 1px solid #4a5568; + border-radius: 8px; + padding: 5px 0; + transition: all 0.3s ease; + } + + #inputFrame:hover { + border-color: #5b6e91; + box-shadow: 0 2px 8px rgba(99, 102, 241, 0.2); + } + + #inputFrame:focus-within { + border-color: #818cf8; + box-shadow: 0 2px 12px rgba(99, 102, 241, 0.3); + } + + /* 登录按钮 - 紫色主题 */ + #loginBtn { + background-color: #6366f1; + color: white; + border-radius: 8px; + padding: 14px 30px; + font-size: 16px; + font-weight: 600; + border: none; + min-width: 240px; + transition: all 0.3s ease; + } + + #loginBtn:hover { + background-color: #4f46e5; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4); + } + + #loginBtn:pressed { + transform: translateY(0); + box-shadow: 0 2px 6px rgba(99, 102, 241, 0.3); + } + """) + + def setup_animations(self): + # 创建输入框动画 + self.username_anim = QPropertyAnimation(self.username_input, b"pos") + self.username_anim.setDuration(300) + self.username_anim.setEasingCurve(QEasingCurve.OutQuad) + + self.password_anim = QPropertyAnimation(self.password_input, b"pos") + self.password_anim.setDuration(300) + self.password_anim.setEasingCurve(QEasingCurve.OutQuad) + + def toggle_password_visibility(self): + if self.password_input.echoMode() == QLineEdit.Password: + self.password_input.setEchoMode(QLineEdit.Normal) + + # 更新眼睛图标 + eye_on_pixmap = QPixmap(":/icons/eye_on.png").scaled(24, 24, Qt.KeepAspectRatio, Qt.SmoothTransformation) + painter = QPainter(eye_on_pixmap) + painter.setCompositionMode(QPainter.CompositionMode_SourceIn) + painter.fillRect(eye_on_pixmap.rect(), Qt.white) + painter.end() + + self.toggle_password_btn.setIcon(QIcon(eye_on_pixmap)) + else: + self.password_input.setEchoMode(QLineEdit.Password) + + # 更新眼睛图标 + eye_off_pixmap = QPixmap(":/icons/eye_off.png").scaled(24, 24, Qt.KeepAspectRatio, Qt.SmoothTransformation) + painter = QPainter(eye_off_pixmap) + painter.setCompositionMode(QPainter.CompositionMode_SourceIn) + painter.fillRect(eye_off_pixmap.rect(), Qt.white) + painter.end() + + self.toggle_password_btn.setIcon(QIcon(eye_off_pixmap)) + + def handle_login(self): + username = self.username_input.text().strip() + password = self.password_input.text().strip() + + # 验证登录 + if not username: + self.show_error("请输入用户名") + self.animate_error(self.username_input) + return + + if not password: + self.show_error("请输入密码") + self.animate_error(self.password_input) + return + + # 实际登录验证 + if username in ["liqi", "qiqi991012"] and password == "qiqi991012": + self.error_label.hide() + self.login_success.emit(username) + else: + self.show_error("用户名或密码错误") + self.animate_error(self.password_input) + + def animate_error(self, input_field): + # 保存原始位置 + original_pos = input_field.pos() + + # 创建抖动动画 + animation = QPropertyAnimation(input_field, b"pos") + animation.setDuration(70) + animation.setLoopCount(3) + animation.setEasingCurve(QEasingCurve.InOutSine) + + # 左右各抖动3次 + positions = [ + QPoint(original_pos.x() + 5, original_pos.y()), + QPoint(original_pos.x() - 5, original_pos.y()), + original_pos + ] + + for i, pos in enumerate(positions): + animation.setKeyValueAt(i / len(positions), pos) + + animation.start() + + def show_error(self, message): + self.error_label.setText(message) + self.error_label.show() + + # 添加红色边框效果(暗色主题版本) + self.username_input.parent().setStyleSheet("#inputFrame { border: 1px solid #fc8181; }") + 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): + self.username_input.parent().setStyleSheet("#inputFrame { border: 1px solid #4a5568; }") + self.password_input.parent().setStyleSheet("#inputFrame { border: 1px solid #4a5568; }") + + def paintEvent(self, event): + # 绘制背景渐变 - 深色主题 + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + + # 创建渐变背景 + gradient = QLinearGradient(0, 0, self.width(), self.height()) + gradient.setColorAt(0, QColor("#1a202c")) + gradient.setColorAt(1, QColor("#2d3748")) + + painter.fillRect(self.rect(), gradient) + + # 绘制装饰性气泡 - 深色主题 + painter.setPen(Qt.NoPen) + + # 左上角大泡 - 使用深紫色 + painter.setBrush(QColor(79, 70, 229, 30)) + painter.drawEllipse(QPoint(50, 70), 150, 150) + + # 左下角中泡 - 使用深蓝色 + painter.setBrush(QColor(49, 46, 129, 40)) + painter.drawEllipse(QPoint(100, 450), 80, 80) + + # 右边中泡 - 使用深青色 + painter.setBrush(QColor(15, 23, 42, 50)) + painter.drawEllipse(QPoint(600, 150), 100, 100) + + # 中央小泡 - 使用深绿色 + painter.setBrush(QColor(6, 78, 59, 60)) + painter.drawEllipse(QPoint(250, 300), 50, 50) + + super().paintEvent(event) \ No newline at end of file diff --git a/v1/views/nav.py b/v1/views/nav.py new file mode 100644 index 0000000..e196113 --- /dev/null +++ b/v1/views/nav.py @@ -0,0 +1,282 @@ +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QFrame, QSpacerItem, QSizePolicy) +from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtGui import QPixmap, QIcon, QFont, QPainter, QBrush, QLinearGradient, QColor + + +class NavBar(QWidget): + nav_item_clicked = pyqtSignal(str) + logout_clicked = pyqtSignal() + + def __init__(self, username): + super().__init__() + self.username = username or "访客" # 设置默认值,防止空字符串 + self.active_btn = None + self.init_ui() + + def init_ui(self): + self.setObjectName("navBar") + self.setFixedWidth(240) + self.setStyleSheet(""" + #navBar { + background-color: #1a202c; + border-right: 1px solid #2d3748; + } + + #userFrame { + background-color: #2d3748; + border-radius: 10px; + margin: 15px; + padding: 15px; + } + + #userName { + font-size: 16px; + font-weight: 600; + color: #e2e8f0; + } + + #navBtn { + text-align: left; + padding: 15px 25px; + color: #a0aec0; + border-radius: 8px; + margin: 5px 15px; + font-size: 14px; + transition: all 0.3s ease; + } + + #navBtn:hover { + background-color: rgba(79, 70, 229, 0.1); + color: #cbd5e0; + padding-left: 28px; + } + + #navBtn.active { + background-color: rgba(79, 70, 229, 0.15); + color: #818cf8; + font-weight: 500; + padding-left: 28px; + } + + #logoutBtn { + text-align: left; + padding: 15px 25px; + border-radius: 8px; + margin: 5px 15px; + font-size: 14px; + color: #feb2b2; + transition: all 0.3s ease; + } + + #logoutBtn:hover { + background-color: rgba(254, 178, 178, 0.1); + color: #f56565; + padding-left: 28px; + } + """) + + layout = QVBoxLayout() + 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) + initial = self.username[0].upper() if self.username else "访" + avatar_label.setText(initial) + avatar_label.setStyleSheet("font-size: 18px; font-weight: bold; color: white;") + + # 用户信息容器 + 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;") + + user_info_layout.addWidget(user_name) + 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": "history", "id": "history"}, + {"name": "个人设置", "icon": "settings", "id": "settings"}, + ] + + # 导航按钮 - 添加图标 + self.nav_buttons = [] + for item in nav_items: + btn = QPushButton() + 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.clicked.connect(lambda _, x=item["id"]: self.nav_item_clicked.emit(x)) + self.nav_buttons.append(btn) + + # 退出按钮 + 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.clicked.connect(self.logout_clicked.emit) + + # 组装布局 + layout.addWidget(user_frame) + layout.addSpacing(20) + + # 添加分割线 + separator = QFrame() + separator.setFrameShape(QFrame.HLine) + separator.setStyleSheet("background-color: #2d3748; margin: 0 15px; height: 1px;") + layout.addWidget(separator) + layout.addSpacing(20) + + # 导航按钮容器 + nav_container = QWidget() + nav_container_layout = QVBoxLayout(nav_container) + nav_container_layout.setContentsMargins(0, 0, 0, 0) + nav_container_layout.setSpacing(5) + + for btn in self.nav_buttons: + nav_container_layout.addWidget(btn) + + layout.addWidget(nav_container) + layout.addStretch() + + # 底部添加退出按钮 + bottom_container = QWidget() + bottom_container_layout = QVBoxLayout(bottom_container) + bottom_container_layout.setContentsMargins(0, 0, 0, 0) + + # 底部添加分割线 + separator_bottom = QFrame() + separator_bottom.setFrameShape(QFrame.HLine) + separator_bottom.setStyleSheet("background-color: #2d3748; margin: 0 15px; height: 1px;") + + bottom_container_layout.addWidget(separator_bottom) + bottom_container_layout.addSpacing(15) + bottom_container_layout.addWidget(logout_btn) + + layout.addWidget(bottom_container) + + self.setLayout(layout) + + # 默认激活首页 + self.set_active_nav("home") + + def set_active_nav(self, nav_id): + # 移除所有按钮的active类 + for btn in self.nav_buttons: + btn.setProperty("class", "") + # 重新设置样式以应用变化 + btn.setStyle(btn.style()) + + # 为活动按钮添加active类 + for btn in self.nav_buttons: + if btn.property("nav_id") == nav_id: + btn.setProperty("class", "active") + + # 更新图标颜色 + icon_label = btn.layout().itemAt(0).widget() + active_pixmap = icon_label.pixmap() + + # 改变图标颜色 + painter = QPainter(active_pixmap) + painter.setCompositionMode(QPainter.CompositionMode_SourceIn) + painter.fillRect(active_pixmap.rect(), QColor("#818cf8")) + painter.end() + + icon_label.setPixmap(active_pixmap) + + # 重新设置样式以应用变化 + btn.setStyle(btn.style()) + + self.active_btn = btn \ No newline at end of file diff --git a/v1/views/word_to_pdf.py b/v1/views/word_to_pdf.py new file mode 100644 index 0000000..f4c7608 --- /dev/null +++ b/v1/views/word_to_pdf.py @@ -0,0 +1,338 @@ +import os +import comtypes.client +import random +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 + + +def generate_unique_filename(original_path): + """生成唯一的文件名格式:原名称_日期_随机数.pdf""" + base_name = os.path.basename(original_path) + name_without_ext = os.path.splitext(base_name)[0] + + # 获取当前日期 + current_date = datetime.datetime.now().strftime("%Y%m%d") + + # 生成4位随机数 + random_num = random.randint(1000, 9999) + + # 组合成新文件名 + new_filename = f"{name_without_ext}_{current_date}_{random_num}.pdf" + return os.path.join(os.path.dirname(original_path), new_filename) + + +class ConversionThread(QThread): + progress_updated = pyqtSignal(int, int, str) # current, total, filename + conversion_done = pyqtSignal(str, bool, str) # filename, success, message + finished_all = pyqtSignal() + + def __init__(self, files): + super().__init__() + self.files = files + + def run(self): + word = None + try: + word = comtypes.client.CreateObject('Word.Application') + word.Visible = False + + total = len(self.files) + for i, file_path in enumerate(self.files): + self.progress_updated.emit(i + 1, total, os.path.basename(file_path)) + + try: + # 使用新的文件名生成逻辑 + pdf_path = generate_unique_filename(file_path) + doc = word.Documents.Open(os.path.abspath(file_path)) + doc.SaveAs(pdf_path, FileFormat=17) # 17 = PDF + doc.Close() + self.conversion_done.emit(file_path, True, pdf_path) # 传递新文件路径 + except Exception as e: + self.conversion_done.emit(file_path, False, str(e)) + + self.finished_all.emit() + finally: + if word: + word.Quit() + + +class WordToPDFPage(QWidget): + def __init__(self): + super().__init__() + self.files = [] + self.converted_files = {} + self.conversion_thread = None + self.init_ui() + + def init_ui(self): + self.setObjectName("wordToPdfPage") + self.setAcceptDrops(True) + + layout = QHBoxLayout() + layout.setContentsMargins(20, 20, 20, 20) + layout.setSpacing(20) + + # 左侧面板 - 文件上传 + left_panel = QFrame() + left_panel.setObjectName("leftPanel") + left_panel.setMinimumWidth(300) + + 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_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.setAlignment(Qt.AlignCenter) + + upload_text = QLabel("拖拽文件或文件夹到此处\n或点击上传") + upload_text.setAlignment(Qt.AlignCenter) + 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(upload_btn) + + # 文件列表 + self.file_list = QListWidget() + self.file_list.setObjectName("fileList") + self.file_list.setSelectionMode(QListWidget.ExtendedSelection) + + # 操作按钮 + btn_layout = QHBoxLayout() + + convert_btn = QPushButton("开始转换") + convert_btn.setObjectName("convertBtn") + 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(delete_btn) + + # 组装左侧面板 + left_layout.addWidget(upload_area) + left_layout.addWidget(self.file_list) + left_layout.addLayout(btn_layout) + + # 右侧面板 - 转换结果 + right_panel = QFrame() + right_panel.setObjectName("rightPanel") + + right_layout = QVBoxLayout(right_panel) + right_layout.setContentsMargins(0, 0, 0, 0) + right_layout.setSpacing(15) + + # 进度条 + self.total_progress = QProgressBar() + self.total_progress.setTextVisible(False) + + self.file_progress = QProgressBar() + self.file_progress.setTextVisible(False) + + # 结果列表 + self.result_list = QListWidget() + self.result_list.setObjectName("resultList") + + # 下载按钮 + self.download_btn = QPushButton("全部下载") + self.download_btn.setObjectName("downloadBtn") + 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_title = QLabel("转换日志") + log_title.setObjectName("logTitle") + + self.log_content = QListWidget() + self.log_content.setObjectName("logContent") + + 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(self.download_btn) + right_layout.addWidget(log_area) + + # 主布局 + layout.addWidget(left_panel) + layout.addWidget(right_panel) + + self.setLayout(layout) + + def dragEnterEvent(self, event: QDragEnterEvent): + if event.mimeData().hasUrls(): + event.acceptProposedAction() + + def dropEvent(self, event: QDropEvent): + urls = event.mimeData().urls() + files = [] + + for url in urls: + path = url.toLocalFile() + if os.path.isdir(path): + # 处理文件夹 + for root, _, filenames in os.walk(path): + for f in filenames: + if f.lower().endswith(('.doc', '.docx')): + files.append(os.path.join(root, f)) + elif os.path.isfile(path) and path.lower().endswith(('.doc', '.docx')): + files.append(path) + + if files: + self.add_files(files) + + 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) + else: + # 如果用户没有选择文件夹,则选择文件 + files, _ = QFileDialog.getOpenFileNames( + self, "选择Word文件", "", "Word Files (*.doc *.docx);;All Files (*)" + ) + if files: + self.add_files(files) + + def add_files(self, files): + """添加文件到列表,避免重复""" + 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) + + def delete_selected(self): + """删除选中的文件""" + for item in self.file_list.selectedItems(): + self.files.remove(item.data(Qt.UserRole)) + self.file_list.takeItem(self.file_list.row(item)) + + def start_conversion(self): + """开始转换文件""" + if not self.files: + QMessageBox.warning(self, "警告", "请先添加要转换的文件!") + return + + # 重置状态 + self.converted_files = {} + self.result_list.clear() + self.log_content.clear() + self.download_btn.setEnabled(False) + + 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) + self.conversion_thread.finished_all.connect(self.handle_finished_all) + self.conversion_thread.start() + + def update_progress(self, current, total, filename): + """更新进度条""" + self.total_progress.setMaximum(total) + self.total_progress.setValue(current) + + self.file_progress.setMaximum(100) + self.file_progress.setValue(0) + + self.log_content.addItem(f"正在转换: {filename} ({current}/{total})") + + def handle_conversion_done(self, file_path, success, message): + """处理单个文件转换完成""" + basename = os.path.basename(file_path) + if success: + # 成功时,message包含新的PDF路径 + pdf_path = message + self.converted_files[file_path] = pdf_path + + # 从待转换列表移除 + for i in range(self.file_list.count()): + item = self.file_list.item(i) + if item.data(Qt.UserRole) == file_path: + self.file_list.takeItem(i) + break + + # 添加到结果列表 + item = QListWidgetItem(f"{basename} → {os.path.basename(pdf_path)}") + item.setData(Qt.UserRole, pdf_path) + self.result_list.addItem(item) + + self.log_content.addItem(f"✅ 转换成功: {basename} -> {os.path.basename(pdf_path)}") + else: + # 失败时显示错误信息 + self.log_content.addItem(f"❌ 转换失败: {basename} - {message}") + + def handle_finished_all(self): + """所有文件转换完成""" + self.log_content.addItem(f"🎉 全部转换完成! 共转换 {len(self.converted_files)} 个文件") + self.download_btn.setEnabled(True) + + def download_all(self): + """下载所有转换后的文件""" + if not self.converted_files: + return + + folder = QFileDialog.getExistingDirectory(self, "选择保存位置") + if not folder: + return + + success = 0 + errors = [] + + for pdf_path in self.converted_files.values(): + try: + file_name = os.path.basename(pdf_path) + destination = os.path.join(folder, file_name) + + # 复制文件 + with open(pdf_path, 'rb') as src_file: + with open(destination, 'wb') as dest_file: + dest_file.write(src_file.read()) + success += 1 + except Exception as e: + errors.append(f"{os.path.basename(pdf_path)}: {str(e)}") + + # 显示结果 + 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}") + else: + QMessageBox.information(self, "下载完成", f"已成功下载 {success} 个文件到 {folder}") + + self.log_content.addItem(f"已下载 {success} 个文件到 {folder}") \ No newline at end of file