初始化

This commit is contained in:
2025-06-07 15:54:17 +08:00
commit 12a583295d
15 changed files with 2486 additions and 0 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

240
v1/styles/styles.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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}")