初始化
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