初始化
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/nl-utils.iml" filepath="$PROJECT_DIR$/.idea/nl-utils.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/nl-utils.iml
generated
Normal file
8
.idea/nl-utils.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
36
README.md
Normal file
36
README.md
Normal file
@@ -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. **使用记录**
|
||||
- 操作记录存储在本地
|
||||
- 侧边栏显示历史记录
|
||||
15
v1/README.md
Normal file
15
v1/README.md
Normal file
@@ -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功能页
|
||||
```
|
||||
327
v1/main.py
Normal file
327
v1/main.py
Normal file
@@ -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()
|
||||
0
v1/requirements.txt
Normal file
0
v1/requirements.txt
Normal file
240
v1/styles/styles.py
Normal file
240
v1/styles/styles.py
Normal file
@@ -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;
|
||||
}
|
||||
"""
|
||||
258
v1/views/app_center.py
Normal file
258
v1/views/app_center.py
Normal file
@@ -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
|
||||
471
v1/views/history.py
Normal file
471
v1/views/history.py
Normal file
@@ -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);
|
||||
}
|
||||
""")
|
||||
482
v1/views/login.py
Normal file
482
v1/views/login.py
Normal file
@@ -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)
|
||||
282
v1/views/nav.py
Normal file
282
v1/views/nav.py
Normal file
@@ -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
|
||||
338
v1/views/word_to_pdf.py
Normal file
338
v1/views/word_to_pdf.py
Normal file
@@ -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}")
|
||||
Reference in New Issue
Block a user