优化规程样式 提供整行hover

main
zcwBit 3 months ago
parent da7c6de0b9
commit c9aa04fa73

File diff suppressed because it is too large Load Diff

@ -0,0 +1,249 @@
/* ==================== DCS2025 规程表格样式 ==================== */
/* ==================== 表格基础样式 ==================== */
QTableView {
background-color: #FFFFFF;
border: 1px solid #E5E7EB;
border-radius: 6px;
gridline-color: #F3F4F6;
font-size: 13px;
color: #374151;
selection-background-color: #EBF8FF;
selection-color: #1E40AF;
outline: none;
}
/* 表格项基础样式 */
QTableView::item {
padding: 8px;
border: none;
min-height: 40px;
}
/* 表格项选中状态 - 整行选中效果 */
QTableView::item:selected {
background-color: #EBF8FF;
color: #1E40AF;
border: none;
}
/* 表格项hover状态 - 整行hover效果 */
QTableView::item:hover {
background-color: #F0F9FF;
color: #1F2937;
}
/* 表格项选中且hover状态 */
QTableView::item:selected:hover {
background-color: #DBEAFE;
color: #1565C0;
}
/* 表格项焦点状态 */
QTableView::item:focus {
background-color: #EBF8FF;
color: #1E40AF;
outline: none;
border: none;
}
/* ==================== 表格头部样式 ==================== */
QHeaderView {
background-color: transparent;
border: none;
outline: none;
}
QHeaderView::section {
background-color: #F8F9FA;
color: #374151;
font-weight: 600;
font-size: 13px;
padding: 12px 8px;
border: 1px solid #E5E7EB;
border-left: none;
text-align: center;
}
QHeaderView::section:first {
border-left: 1px solid #E5E7EB;
border-top-left-radius: 6px;
}
QHeaderView::section:last {
border-top-right-radius: 6px;
}
QHeaderView::section:hover {
background-color: #F3F4F6;
color: #1F2937;
}
QHeaderView::section:pressed {
background-color: #E5E7EB;
color: #111827;
}
/* ==================== 垂直表头隐藏 ==================== */
QHeaderView::section:vertical {
border: none;
background-color: transparent;
}
/* ==================== 滚动条样式 ==================== */
QScrollBar:vertical {
background-color: #F9FAFB;
width: 12px;
border-radius: 6px;
border: none;
}
QScrollBar::handle:vertical {
background-color: #D1D5DB;
border-radius: 6px;
min-height: 20px;
margin: 2px;
}
QScrollBar::handle:vertical:hover {
background-color: #9CA3AF;
}
QScrollBar::handle:vertical:pressed {
background-color: #6B7280;
}
QScrollBar::add-line:vertical,
QScrollBar::sub-line:vertical {
border: none;
background: none;
height: 0px;
}
QScrollBar::add-page:vertical,
QScrollBar::sub-page:vertical {
background: none;
}
QScrollBar:horizontal {
background-color: #F9FAFB;
height: 12px;
border-radius: 6px;
border: none;
}
QScrollBar::handle:horizontal {
background-color: #D1D5DB;
border-radius: 6px;
min-width: 20px;
margin: 2px;
}
QScrollBar::handle:horizontal:hover {
background-color: #9CA3AF;
}
QScrollBar::handle:horizontal:pressed {
background-color: #6B7280;
}
QScrollBar::add-line:horizontal,
QScrollBar::sub-line:horizontal {
border: none;
background: none;
width: 0px;
}
QScrollBar::add-page:horizontal,
QScrollBar::sub-page:horizontal {
background: none;
}
/* ==================== 特殊状态样式 ==================== */
/* 执行成功的行 - 绿色主题 */
QTableView::item[executionStatus="success"] {
background-color: #ECFDF5;
color: #065F46;
}
QTableView::item[executionStatus="success"]:hover {
background-color: #D1FAE5;
color: #047857;
}
QTableView::item[executionStatus="success"]:selected {
background-color: #A7F3D0;
color: #064E3B;
}
/* 执行失败的行 - 红色主题 */
QTableView::item[executionStatus="failed"] {
background-color: #FEF2F2;
color: #991B1B;
}
QTableView::item[executionStatus="failed"]:hover {
background-color: #FEE2E2;
color: #7F1D1D;
}
QTableView::item[executionStatus="failed"]:selected {
background-color: #FECACA;
color: #7F1D1D;
}
/* 主步骤行 - 灰色主题 */
QTableView::item[stepType="main"] {
background-color: #F9FAFB;
color: #374151;
font-weight: 600;
}
QTableView::item[stepType="main"]:hover {
background-color: #F3F4F6;
color: #1F2937;
}
QTableView::item[stepType="main"]:selected {
background-color: #E5E7EB;
color: #111827;
}
/* ==================== 网格线样式 ==================== */
QTableView {
gridline-color: #F3F4F6;
show-decoration-selected: 1;
}
/* ==================== 文本对齐 ==================== */
QTableView::item[textAlign="center"] {
text-align: center;
}
QTableView::item[textAlign="left"] {
text-align: left;
}
QTableView::item[textAlign="right"] {
text-align: right;
}
/* ==================== 响应式设计 ==================== */
QTableView[size="compact"] {
font-size: 12px;
}
QTableView[size="compact"]::item {
padding: 6px;
min-height: 32px;
}
QTableView[size="large"] {
font-size: 14px;
}
QTableView[size="large"]::item {
padding: 10px;
min-height: 48px;
}

@ -0,0 +1,390 @@
/* ==================== DCS2025 步骤执行器样式 ==================== */
/* ==================== 执行按钮样式 ==================== */
/* 开始自动执行按钮 - 绿色主题 */
QPushButton#autoExecuteBtn {
color: #047857;
font-size: 15px;
font-weight: 600;
padding: 8px 16px;
background-color: #ECFDF5;
border-radius: 6px;
border: 1px solid #BBF7D0;
font-family: "PingFangSC-Medium", "Microsoft YaHei", sans-serif;
min-width: 100px;
min-height: 36px;
}
QPushButton#autoExecuteBtn:hover {
background-color: #D1FAE5;
border-color: #86EFAC;
color: #065F46;
}
QPushButton#autoExecuteBtn:pressed {
background-color: #A7F3D0;
border-color: #6EE7B7;
color: #064E3B;
}
QPushButton#autoExecuteBtn:disabled {
background-color: #F3F4F6;
color: #9CA3AF;
border-color: #E5E7EB;
}
/* 停止自动执行按钮 - 红色主题 */
QPushButton#stopExecuteBtn {
color: #B91C1C;
font-size: 15px;
font-weight: 600;
padding: 8px 16px;
background-color: #FEF2F2;
border-radius: 6px;
border: 1px solid #FECACA;
font-family: "PingFangSC-Medium", "Microsoft YaHei", sans-serif;
min-width: 100px;
min-height: 36px;
}
QPushButton#stopExecuteBtn:hover {
background-color: #FEE2E2;
border-color: #FCA5A5;
color: #991B1B;
}
QPushButton#stopExecuteBtn:pressed {
background-color: #FECACA;
border-color: #F87171;
color: #7F1D1D;
}
QPushButton#stopExecuteBtn:disabled {
background-color: #F3F4F6;
color: #9CA3AF;
border-color: #E5E7EB;
}
/* 执行下一步按钮 - 蓝色主题 */
QPushButton#nextStepBtn {
color: #1D4ED8;
font-size: 15px;
font-weight: 600;
padding: 8px 16px;
background-color: #EBF8FF;
border-radius: 6px;
border: 1px solid #BFDBFE;
font-family: "PingFangSC-Medium", "Microsoft YaHei", sans-serif;
min-width: 100px;
min-height: 36px;
}
QPushButton#nextStepBtn:hover {
background-color: #DBEAFE;
border-color: #93C5FD;
color: #1E40AF;
}
QPushButton#nextStepBtn:pressed {
background-color: #BFDBFE;
border-color: #60A5FA;
color: #1E3A8A;
}
QPushButton#nextStepBtn:disabled {
background-color: #F3F4F6;
color: #9CA3AF;
border-color: #E5E7EB;
}
/* 完全重置按钮 - 橙色主题 */
QPushButton#resetExecuteBtn {
color: #D97706;
font-size: 15px;
font-weight: 600;
padding: 8px 16px;
background-color: #FEF3C7;
border-radius: 6px;
border: 1px solid #FCD34D;
font-family: "PingFangSC-Medium", "Microsoft YaHei", sans-serif;
min-width: 100px;
min-height: 36px;
}
QPushButton#resetExecuteBtn:hover {
background-color: #FDE68A;
border-color: #FBBF24;
color: #B45309;
}
QPushButton#resetExecuteBtn:pressed {
background-color: #FCD34D;
border-color: #F59E0B;
color: #92400E;
}
QPushButton#resetExecuteBtn:disabled {
background-color: #F3F4F6;
color: #9CA3AF;
border-color: #E5E7EB;
}
/* 生成报告按钮 - 紫色主题 */
QPushButton#exportReportBtn {
color: #7C3AED;
font-size: 15px;
font-weight: 600;
padding: 8px 16px;
background-color: #F3E8FF;
border-radius: 6px;
border: 1px solid #C4B5FD;
font-family: "PingFangSC-Medium", "Microsoft YaHei", sans-serif;
min-width: 100px;
min-height: 36px;
}
QPushButton#exportReportBtn:hover {
background-color: #EDE9FE;
border-color: #A78BFA;
color: #6D28D9;
}
QPushButton#exportReportBtn:pressed {
background-color: #DDD6FE;
border-color: #8B5CF6;
color: #5B21B6;
}
QPushButton#exportReportBtn:disabled {
background-color: #F3F4F6;
color: #9CA3AF;
border-color: #E5E7EB;
}
/* ==================== 状态标签样式 ==================== */
/* 状态标签基础样式 */
QLabel#statusLabel {
font-size: 14px;
font-weight: 600;
padding: 6px 12px;
border-radius: 4px;
background-color: #EBF8FF;
color: #1E40AF;
border: 1px solid #BFDBFE;
}
/* 执行状态标签 */
QLabel#executionStatusLabel {
font-size: 14px;
font-weight: 600;
padding: 6px 12px;
border-radius: 4px;
border: 1px solid;
}
/* 成功状态 */
QLabel#executionStatusLabel[status="success"] {
background-color: #ECFDF5;
color: #065F46;
border-color: #BBF7D0;
}
/* 错误状态 */
QLabel#executionStatusLabel[status="error"] {
background-color: #FEF2F2;
color: #991B1B;
border-color: #FECACA;
}
/* 警告状态 */
QLabel#executionStatusLabel[status="warning"] {
background-color: #FEF3C7;
color: #92400E;
border-color: #FCD34D;
}
/* 信息状态 */
QLabel#executionStatusLabel[status="info"] {
background-color: #EBF8FF;
color: #1E40AF;
border-color: #BFDBFE;
}
/* ==================== 倒计时标签样式 ==================== */
QLabel#countdownLabel {
font-size: 13px;
font-weight: 500;
padding: 4px 8px;
border-radius: 4px;
font-family: "Consolas", "Monaco", monospace;
}
/* 剩余时间充足 */
QLabel#countdownLabel[timeRemaining="high"] {
background-color: #ECFDF5;
color: #065F46;
border: 1px solid #BBF7D0;
}
/* 剩余时间中等 */
QLabel#countdownLabel[timeRemaining="medium"] {
background-color: #FEF3C7;
color: #92400E;
border: 1px solid #FCD34D;
}
/* 剩余时间不足 */
QLabel#countdownLabel[timeRemaining="low"] {
background-color: #FEF2F2;
color: #991B1B;
border: 1px solid #FECACA;
}
/* 已完成 */
QLabel#countdownLabel[timeRemaining="completed"] {
background-color: #EBF8FF;
color: #1E40AF;
border: 1px solid #BFDBFE;
}
/* ==================== 规程信息区域样式 ==================== */
/* 规程信息容器 */
QWidget#procedureInfoContainer {
background-color: #F8F9FA;
border: 1px solid #E5E7EB;
border-radius: 6px;
}
/* 超紧凑信息组 */
QWidget#ultraCompactInfoGroup {
background-color: transparent;
}
/* 超紧凑信息标签 */
QLabel#ultraCompactInfoLabel {
font-size: 12px;
font-weight: 600;
color: #6B7280;
}
/* 超紧凑信息值 */
QLabel#ultraCompactInfoValue {
font-size: 12px;
font-weight: 500;
color: #374151;
}
/* ==================== 表格样式 ==================== */
/* 表格视图基础样式 */
QTableView {
background-color: #FFFFFF;
border: 1px solid #E5E7EB;
border-radius: 6px;
gridline-color: #F3F4F6;
selection-background-color: #EBF8FF;
selection-color: #1E40AF;
font-size: 13px;
color: #374151;
}
QTableView::item {
padding: 8px;
border: none;
}
QTableView::item:hover {
background-color: #F9FAFB;
}
QTableView::item:selected {
background-color: #EBF8FF;
color: #1E40AF;
}
/* 表格头部样式 */
QHeaderView::section {
background-color: #F3F4F6;
color: #374151;
font-weight: 600;
font-size: 13px;
padding: 8px;
border: 1px solid #E5E7EB;
border-left: none;
}
QHeaderView::section:first {
border-left: 1px solid #E5E7EB;
}
QHeaderView::section:hover {
background-color: #E5E7EB;
}
/* ==================== 输入控件样式 ==================== */
/* 数值输入框 */
QSpinBox, QDoubleSpinBox {
background-color: #FFFFFF;
border: 1px solid #E5E7EB;
border-radius: 4px;
padding: 4px 8px;
font-size: 13px;
color: #374151;
min-width: 60px;
}
QSpinBox:focus, QDoubleSpinBox:focus {
border-color: #3B82F6;
outline: none;
}
/* 复选框 */
QCheckBox {
font-size: 13px;
color: #374151;
spacing: 6px;
}
QCheckBox::indicator {
width: 16px;
height: 16px;
border: 1px solid #D1D5DB;
border-radius: 3px;
background-color: #FFFFFF;
}
QCheckBox::indicator:checked {
background-color: #3B82F6;
border-color: #3B82F6;
image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIHZpZXdCb3g9IjAgMCAxMiAxMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEwIDNMNC41IDguNUwyIDYiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPgo=);
}
QCheckBox::indicator:hover {
border-color: #9CA3AF;
}
/* ==================== 标签样式 ==================== */
QLabel {
font-size: 13px;
color: #374151;
}
/* 标题标签 */
QLabel[class="title"] {
font-size: 16px;
font-weight: 600;
color: #1F2937;
}
/* 副标题标签 */
QLabel[class="subtitle"] {
font-size: 14px;
font-weight: 500;
color: #4B5563;
}

@ -7,7 +7,7 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QTableView, QToolButton,
QLabel, QCheckBox, QSpinBox, QMenu, QFileDialog,
QTabWidget, QTabBar, QListWidget, QListWidgetItem, QDialog,
QLineEdit, QFormLayout, QDialogButtonBox, QMessageBox,
QHeaderView, QToolBar, QAction, QStatusBar, QSplitter, QAbstractItemView)
QHeaderView, QToolBar, QAction, QStatusBar, QSplitter, QAbstractItemView, QFrame)
from docx import Document
from docx.shared import Pt, RGBColor
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
@ -16,7 +16,7 @@ import qtawesome as qta
from datetime import datetime
# 导入其他模块
from model.ProcedureModel.ProcedureProcessor import ExcelParser, StepTableModel
from model.ProcedureModel.ProcedureProcessor import ExcelParser
from utils.DBModels.ProcedureModel import DatabaseManager
from UI.ProcedureManager.HistoryViewer import HistoryViewerWidget
from UI.ProcedureManager.StepExecutor import StepExecutor # 修改导入路径
@ -80,35 +80,134 @@ class ProcedureManager(QMainWindow):
self.setWindowTitle("规程管理系统")
self.setGeometry(100, 100, 1200, 800)
# 创建工具栏 - 优化图标大小和布局
self.toolbar = QToolBar("主工具栏")
self.toolbar.setIconSize(QSize(12, 12)) # 减小图标尺寸避免遮盖
self.toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) # 文字在图标旁边
self.toolbar.setMaximumHeight(50) # 适当的工具栏高度
self.toolbar.setContentsMargins(0, 0, 0, 0) # 移除边距
self.addToolBar(self.toolbar)
self.createActions()
# 初始化数据库
self.initDB()
# 创建顶部按钮布局
self.createTopButtonLayout()
def setupButtonIcon(self, button, icon_name, color):
"""设置按钮图标,支持不同状态的颜色变化"""
# 正常状态图标
normal_icon = qta.icon(icon_name, color=color)
# 悬停状态图标(稍微亮一些)
hover_color = self.lightenColor(color, 0.2)
hover_icon = qta.icon(icon_name, color=hover_color)
# 按下状态图标(稍微暗一些)
pressed_color = self.darkenColor(color, 0.2)
pressed_icon = qta.icon(icon_name, color=pressed_color)
# 设置图标
button.setIcon(normal_icon)
# 存储不同状态的图标,用于状态切换
button._normal_icon = normal_icon
button._hover_icon = hover_icon
button._pressed_icon = pressed_icon
# 连接事件
button.enterEvent = lambda event: self.onButtonEnter(button, event)
button.leaveEvent = lambda event: self.onButtonLeave(button, event)
button.mousePressEvent = lambda event: self.onButtonPress(button, event)
button.mouseReleaseEvent = lambda event: self.onButtonRelease(button, event)
def lightenColor(self, color, factor):
"""使颜色变亮"""
if color.startswith('#'):
# 十六进制颜色
r = int(color[1:3], 16)
g = int(color[3:5], 16)
b = int(color[5:7], 16)
r = min(255, int(r + (255 - r) * factor))
g = min(255, int(g + (255 - g) * factor))
b = min(255, int(b + (255 - b) * factor))
return f"#{r:02x}{g:02x}{b:02x}"
return color
def darkenColor(self, color, factor):
"""使颜色变暗"""
if color.startswith('#'):
# 十六进制颜色
r = int(color[1:3], 16)
g = int(color[3:5], 16)
b = int(color[5:7], 16)
r = max(0, int(r * (1 - factor)))
g = max(0, int(g * (1 - factor)))
b = max(0, int(b * (1 - factor)))
return f"#{r:02x}{g:02x}{b:02x}"
return color
def onButtonEnter(self, button, event):
"""按钮鼠标进入事件"""
if hasattr(button, '_hover_icon'):
button.setIcon(button._hover_icon)
# 调用原始的enterEvent
QPushButton.enterEvent(button, event)
def onButtonLeave(self, button, event):
"""按钮鼠标离开事件"""
if hasattr(button, '_normal_icon'):
button.setIcon(button._normal_icon)
# 调用原始的leaveEvent
QPushButton.leaveEvent(button, event)
def onButtonPress(self, button, event):
"""按钮鼠标按下事件"""
if hasattr(button, '_pressed_icon'):
button.setIcon(button._pressed_icon)
# 调用原始的mousePressEvent
QPushButton.mousePressEvent(button, event)
def onButtonRelease(self, button, event):
"""按钮鼠标释放事件"""
# 检查鼠标是否还在按钮上
if button.rect().contains(event.pos()):
if hasattr(button, '_hover_icon'):
button.setIcon(button._hover_icon)
else:
if hasattr(button, '_normal_icon'):
button.setIcon(button._normal_icon)
# 调用原始的mouseReleaseEvent
QPushButton.mouseReleaseEvent(button, event)
# 添加状态栏
self.statusBar = QStatusBar()
self.setStatusBar(self.statusBar)
# 批量执行相关变量
self.batchExecutionQueue = [] # 批量执行队列
self.currentBatchIndex = 0 # 当前执行的规程索引
self.isBatchExecuting = False # 是否正在批量执行
self.batchExecutionTimer = QTimer() # 批量执行检查定时器
self.batchExecutionTimer.timeout.connect(self.checkBatchExecutionStatus)
self.currentBatchTabIndex = -1 # 当前批量执行的标签页索引
def initUI(self):
# 加载样式表
self.loadStylesheet()
# 创建主容器
mainContainer = QWidget()
mainLayout = QVBoxLayout()
mainLayout.setSpacing(0)
mainLayout.setContentsMargins(0, 0, 0, 0)
# 添加顶部按钮布局
mainLayout.addWidget(self.topButtonWidget)
# 创建主控件 - 已经是QTabWidget
self.tabs = QTabWidget()
self.setCentralWidget(self.tabs)
# 设置标签页样式,消除边距
self.tabs.setStyleSheet("""
QTabWidget::pane {
border: none;
margin: 0px;
padding: 0px;
}
QTabWidget::tab-bar {
alignment: left;
}
""")
mainLayout.addWidget(self.tabs)
# 设置主容器
mainContainer.setLayout(mainLayout)
self.setCentralWidget(mainContainer)
# 启用标签页关闭按钮
self.tabs.setTabsClosable(True)
@ -127,6 +226,18 @@ class ProcedureManager(QMainWindow):
# 初始化标签锁定状态
self.activeExecutorIndex = -1
self.loadStylesheet()
self.statusBar = QStatusBar()
self.setStatusBar(self.statusBar)
# 批量执行相关变量
self.batchExecutionQueue = [] # 批量执行队列
self.currentBatchIndex = 0 # 当前执行的规程索引
self.isBatchExecuting = False # 是否正在批量执行
self.batchExecutionTimer = QTimer() # 批量执行检查定时器
self.batchExecutionTimer.timeout.connect(self.checkBatchExecutionStatus)
self.currentBatchTabIndex = -1 # 当前批量执行的标签页索引
def initDB(self):
# 初始化数据库
@ -228,20 +339,18 @@ class ProcedureManager(QMainWindow):
mainWidget = QWidget()
mainWidget.setStyleSheet("background-color: #F5F7FA;")
# 主布局 - 优化边距去除空白
# 主布局 - 删除边距以消除空白区域
mainLayout = QHBoxLayout()
mainLayout.setSpacing(8)
mainLayout.setContentsMargins(8, 8, 8, 8)
mainLayout.setSpacing(4)
mainLayout.setContentsMargins(0, 0, 0, 0)
# 左侧分类卡片
categoryCard = QWidget()
categoryCard.setObjectName("categoryCard")
categoryCard.setFixedWidth(280)
categoryCard.setMaximumHeight(600)
categoryLayout = QVBoxLayout()
categoryLayout.setSpacing(16)
categoryLayout.setContentsMargins(0, 0, 0, 0)
categoryLayout.setSpacing(8)
categoryLayout.setContentsMargins(8, 8, 8, 8)
# 分类标题
categoryTitle = QLabel("规程分类")
@ -262,22 +371,6 @@ class ProcedureManager(QMainWindow):
categoryLayout.addWidget(self.categoryList)
# 分类操作按钮
categoryButtonLayout = QHBoxLayout()
categoryButtonLayout.setSpacing(8)
addCategoryBtn = QPushButton("添加分类")
addCategoryBtn.setObjectName("addCategoryBtn")
addCategoryBtn.clicked.connect(self.addCategory)
addCategoryBtn.setIcon(qta.icon('fa5s.plus', color='white'))
deleteCategoryBtn = QPushButton("删除分类")
deleteCategoryBtn.setObjectName("deleteCategoryBtn")
deleteCategoryBtn.clicked.connect(self.deleteCurrentCategory)
deleteCategoryBtn.setIcon(qta.icon('fa5s.trash', color='white'))
categoryButtonLayout.addWidget(addCategoryBtn)
categoryButtonLayout.addWidget(deleteCategoryBtn)
categoryLayout.addLayout(categoryButtonLayout)
categoryCard.setLayout(categoryLayout)
@ -286,8 +379,8 @@ class ProcedureManager(QMainWindow):
procedureCard.setObjectName("procedureCard")
procedureLayout = QVBoxLayout()
procedureLayout.setSpacing(16)
procedureLayout.setContentsMargins(0, 0, 0, 0)
procedureLayout.setSpacing(8)
procedureLayout.setContentsMargins(8, 8, 8, 8)
# 规程标题和统计信息
procedureHeaderLayout = QHBoxLayout()
@ -338,37 +431,7 @@ class ProcedureManager(QMainWindow):
procedureLayout.addWidget(self.procedureList)
# 规程操作按钮
procedureButtonLayout = QHBoxLayout()
procedureButtonLayout.setSpacing(8)
importBtn = QPushButton("导入规程")
importBtn.setObjectName("importBtn")
importBtn.clicked.connect(self.importProcedure)
importBtn.setIcon(qta.icon('fa5s.file-import', color='white'))
openBtn = QPushButton("打开规程")
openBtn.setObjectName("openBtn")
openBtn.clicked.connect(self.openProcedureInExecutor)
openBtn.setIcon(qta.icon('fa5s.folder-open', color='white'))
exportBtn = QPushButton("导出规程")
exportBtn.setObjectName("exportBtn")
exportBtn.clicked.connect(self.exportSelectedProcedure)
exportBtn.setIcon(qta.icon('fa5s.file-export', color='white'))
deleteBtn = QPushButton("删除规程")
deleteBtn.setObjectName("deleteBtn")
deleteBtn.clicked.connect(self.deleteSelectedProcedure)
deleteBtn.setIcon(qta.icon('fa5s.trash', color='white'))
procedureButtonLayout.addWidget(importBtn)
procedureButtonLayout.addWidget(openBtn)
procedureButtonLayout.addWidget(exportBtn)
procedureButtonLayout.addWidget(deleteBtn)
procedureButtonLayout.addStretch()
procedureLayout.addLayout(procedureButtonLayout)
procedureCard.setLayout(procedureLayout)
@ -379,152 +442,126 @@ class ProcedureManager(QMainWindow):
mainWidget.setLayout(mainLayout)
self.tabs.addTab(mainWidget, "规程管理")
def getButtonStyle(self, bg_color, border_color, hover_bg, hover_border, pressed_bg, pressed_border):
"""生成统一的按钮样式"""
return f"""
QToolButton {{
background-color: {bg_color};
border: 1px solid {border_color};
border-radius: 6px;
padding: 6px 12px;
color: #FFFFFF;
font-size: 13px;
font-weight: 600;
min-width: 80px;
min-height: 32px;
font-family: "Microsoft YaHei", sans-serif;
}}
QToolButton:hover {{
background-color: {hover_bg};
border-color: {hover_border};
}}
QToolButton:pressed {{
background-color: {pressed_bg};
border-color: {pressed_border};
}}
"""
def createActions(self):
def createTopButtonLayout(self):
"""创建顶部按钮布局"""
# 创建顶部按钮容器
self.topButtonWidget = QWidget()
self.topButtonWidget.setObjectName("topButtonWidget")
self.topButtonWidget.setFixedHeight(60)
self.topButtonWidget.setStyleSheet("""
QWidget#topButtonWidget {
background-color: #FFFFFF;
border-bottom: 1px solid #E5E7EB;
}
""")
# 创建横向布局
buttonLayout = QHBoxLayout()
buttonLayout.setSpacing(8)
buttonLayout.setContentsMargins(16, 8, 16, 8)
# 导入规程按钮 - 绿色主题
self.importBtn = QToolButton()
self.importBtn = QPushButton("导入规程")
self.importBtn.setObjectName("importToolBtn")
self.importBtn.setIcon(qta.icon('fa5s.file-import', color='#FFFFFF'))
self.importBtn.setText("导入规程")
self.importBtn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.importBtn.setIconSize(QSize(18, 18))
self.importBtn.setStatusTip("导入Excel规程文件")
self.importBtn.setStyleSheet(self.getButtonStyle(
"#10B981", "#059669", "#059669", "#047857", "#047857", "#065F46"
))
self.importBtn.clicked.connect(self.importProcedure)
self.toolbar.addWidget(self.importBtn)
self.setupButtonIcon(self.importBtn, 'fa5s.file-import', '#047857')
buttonLayout.addWidget(self.importBtn)
# 添加分类按钮 - 蓝色主题
self.addCategoryBtn = QToolButton()
self.addCategoryBtn = QPushButton("添加分类")
self.addCategoryBtn.setObjectName("addCategoryToolBtn")
self.addCategoryBtn.setIcon(qta.icon('fa5s.folder-plus', color='#FFFFFF'))
self.addCategoryBtn.setText("添加分类")
self.addCategoryBtn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.addCategoryBtn.setIconSize(QSize(18, 18))
self.addCategoryBtn.setStatusTip("添加新的分类")
self.addCategoryBtn.setStyleSheet(self.getButtonStyle(
"#3B82F6", "#2563EB", "#2563EB", "#1D4ED8", "#1D4ED8", "#1E40AF"
))
self.addCategoryBtn.clicked.connect(self.addCategory)
self.toolbar.addWidget(self.addCategoryBtn)
self.setupButtonIcon(self.addCategoryBtn, 'fa5s.folder-plus', '#1D4ED8')
buttonLayout.addWidget(self.addCategoryBtn)
# 删除分类按钮 - 红色主题
self.deleteCategoryBtn = QToolButton()
self.deleteCategoryBtn = QPushButton("删除分类")
self.deleteCategoryBtn.setObjectName("deleteCategoryToolBtn")
self.deleteCategoryBtn.setIcon(qta.icon('fa5s.folder-minus', color='#FFFFFF'))
self.deleteCategoryBtn.setText("删除分类")
self.deleteCategoryBtn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.deleteCategoryBtn.setIconSize(QSize(18, 18))
self.deleteCategoryBtn.setStatusTip("删除当前分类")
self.deleteCategoryBtn.setStyleSheet(self.getButtonStyle(
"#EF4444", "#DC2626", "#DC2626", "#B91C1C", "#B91C1C", "#991B1B"
))
self.deleteCategoryBtn.clicked.connect(self.deleteCurrentCategory)
self.toolbar.addWidget(self.deleteCategoryBtn)
self.setupButtonIcon(self.deleteCategoryBtn, 'fa5s.folder-minus', '#B91C1C')
buttonLayout.addWidget(self.deleteCategoryBtn)
self.toolbar.addSeparator()
# 添加分隔符
separator1 = QFrame()
separator1.setFrameShape(QFrame.VLine)
separator1.setFrameShadow(QFrame.Sunken)
separator1.setStyleSheet("QFrame { color: #E5E7EB; }")
buttonLayout.addWidget(separator1)
# 打开规程按钮 - 蓝色主题
self.openProcedureBtn = QToolButton()
self.openProcedureBtn = QPushButton("打开规程")
self.openProcedureBtn.setObjectName("openProcedureToolBtn")
self.openProcedureBtn.setIcon(qta.icon('fa5s.folder-open', color='#FFFFFF'))
self.openProcedureBtn.setText("打开规程")
self.openProcedureBtn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.openProcedureBtn.setIconSize(QSize(18, 18))
self.openProcedureBtn.setStatusTip("在步骤执行工具中打开选中的规程")
self.openProcedureBtn.setStyleSheet(self.getButtonStyle(
"#3B82F6", "#2563EB", "#2563EB", "#1D4ED8", "#1D4ED8", "#1E40AF"
))
self.openProcedureBtn.clicked.connect(self.openProcedureInExecutor)
self.toolbar.addWidget(self.openProcedureBtn)
self.setupButtonIcon(self.openProcedureBtn, 'fa5s.folder-open', '#1D4ED8')
buttonLayout.addWidget(self.openProcedureBtn)
# 删除规程按钮 - 红色主题
self.deleteProcedureBtn = QToolButton()
self.deleteProcedureBtn = QPushButton("删除规程")
self.deleteProcedureBtn.setObjectName("deleteProcedureToolBtn")
self.deleteProcedureBtn.setIcon(qta.icon('fa5s.trash', color='#FFFFFF'))
self.deleteProcedureBtn.setText("删除规程")
self.deleteProcedureBtn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.deleteProcedureBtn.setIconSize(QSize(18, 18))
self.deleteProcedureBtn.setStatusTip("删除选中的规程")
self.deleteProcedureBtn.setStyleSheet(self.getButtonStyle(
"#EF4444", "#DC2626", "#DC2626", "#B91C1C", "#B91C1C", "#991B1B"
))
self.deleteProcedureBtn.clicked.connect(self.deleteSelectedProcedure)
self.toolbar.addWidget(self.deleteProcedureBtn)
self.setupButtonIcon(self.deleteProcedureBtn, 'fa5s.trash', '#B91C1C')
buttonLayout.addWidget(self.deleteProcedureBtn)
self.toolbar.addSeparator()
# 添加分隔符
separator2 = QFrame()
separator2.setFrameShape(QFrame.VLine)
separator2.setFrameShadow(QFrame.Sunken)
separator2.setStyleSheet("QFrame { color: #E5E7EB; }")
buttonLayout.addWidget(separator2)
# 历史记录按钮 - 紫色主题
self.historyBtn = QToolButton()
self.historyBtn = QPushButton("历史记录")
self.historyBtn.setObjectName("historyToolBtn")
self.historyBtn.setIcon(qta.icon('fa5s.history', color='#FFFFFF'))
self.historyBtn.setText("历史记录")
self.historyBtn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.historyBtn.setIconSize(QSize(18, 18))
self.historyBtn.setStatusTip("查看历史执行记录")
self.historyBtn.setStyleSheet(self.getButtonStyle(
"#8B5CF6", "#7C3AED", "#7C3AED", "#6D28D9", "#6D28D9", "#5B21B6"
))
self.historyBtn.clicked.connect(self.openHistoryViewer)
self.toolbar.addWidget(self.historyBtn)
self.setupButtonIcon(self.historyBtn, 'fa5s.history', '#7C3AED')
buttonLayout.addWidget(self.historyBtn)
# 批量执行按钮 - 绿色主题
self.batchExecuteBtn = QToolButton()
self.batchExecuteBtn = QPushButton("批量执行")
self.batchExecuteBtn.setObjectName("batchExecuteToolBtn")
self.batchExecuteBtn.setIcon(qta.icon('fa5s.play-circle', color='#FFFFFF'))
self.batchExecuteBtn.setText("批量执行")
self.batchExecuteBtn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.batchExecuteBtn.setIconSize(QSize(18, 18))
self.batchExecuteBtn.setStatusTip("按顺序批量执行当前分类中的所有规程")
self.batchExecuteBtn.setStyleSheet(self.getButtonStyle(
"#10B981", "#059669", "#059669", "#047857", "#047857", "#065F46"
))
self.batchExecuteBtn.clicked.connect(self.batchExecuteProcedures)
self.toolbar.addWidget(self.batchExecuteBtn)
self.setupButtonIcon(self.batchExecuteBtn, 'fa5s.play-circle', '#047857')
buttonLayout.addWidget(self.batchExecuteBtn)
# 关键词管理按钮 - 紫色主题
self.keywordManageBtn = QToolButton()
self.keywordManageBtn = QPushButton("关键词管理")
self.keywordManageBtn.setObjectName("keywordManageToolBtn")
self.keywordManageBtn.setIcon(qta.icon('fa5s.key', color='#FFFFFF'))
self.keywordManageBtn.setText("关键词管理")
self.keywordManageBtn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.keywordManageBtn.setIconSize(QSize(18, 18))
self.keywordManageBtn.setStatusTip("管理执行步骤关键词字段库")
self.keywordManageBtn.setStyleSheet(self.getButtonStyle(
"#8B5CF6", "#7C3AED", "#7C3AED", "#6D28D9", "#6D28D9", "#5B21B6"
))
self.keywordManageBtn.clicked.connect(self.openKeywordManager)
self.toolbar.addWidget(self.keywordManageBtn)
self.setupButtonIcon(self.keywordManageBtn, 'fa5s.key', '#7C3AED')
buttonLayout.addWidget(self.keywordManageBtn)
# 导出规程按钮 - 橙色主题
self.exportProcedureBtn = QToolButton()
self.exportProcedureBtn = QPushButton("导出规程")
self.exportProcedureBtn.setObjectName("exportProcedureToolBtn")
self.exportProcedureBtn.setIcon(qta.icon('fa5s.file-export', color='#FFFFFF'))
self.exportProcedureBtn.setText("导出规程")
self.exportProcedureBtn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.exportProcedureBtn.setIconSize(QSize(18, 18))
self.exportProcedureBtn.setStatusTip("导出规程为Excel文件")
self.exportProcedureBtn.setStyleSheet(self.getButtonStyle(
"#F59E0B", "#D97706", "#D97706", "#B45309", "#B45309", "#92400E"
))
self.exportProcedureBtn.clicked.connect(self.exportSelectedProcedure)
self.toolbar.addWidget(self.exportProcedureBtn)
self.setupButtonIcon(self.exportProcedureBtn, 'fa5s.file-export', '#D97706')
buttonLayout.addWidget(self.exportProcedureBtn)
# 添加弹性空间,将按钮推到左侧
buttonLayout.addStretch()
# 设置布局
self.topButtonWidget.setLayout(buttonLayout)
def loadCategories(self):
self.categoryList.clear()
@ -758,31 +795,18 @@ class ProcedureManager(QMainWindow):
def loadStylesheet(self):
qssPath = "Static/Procedure.qss"
try:
# 尝试加载优化版QSS如果不存在则使用原版QSS
optimizedQssPath = "Static/Procedure_optimized.qss"
qssFile = QFile(optimizedQssPath)
if qssFile.exists():
qssFile.open(QFile.ReadOnly | QFile.Text)
stream = QTextStream(qssFile)
stream.setCodec("UTF-8")
self.setStyleSheet(stream.readAll())
qssFile.close()
print("已加载优化版样式表")
else:
# 回退到原始QSS
qssFile = QFile(qssPath)
if qssFile.exists():
qssFile.open(QFile.ReadOnly | QFile.Text)
stream = QTextStream(qssFile)
stream.setCodec("UTF-8")
self.setStyleSheet(stream.readAll())
qssFile.close()
print(f"成功加载样式表: {qssPath}")
qssFile = QFile(qssPath)
if qssFile.exists():
qssFile.open(QFile.ReadOnly | QFile.Text)
stream = QTextStream(qssFile)
stream.setCodec("UTF-8")
self.setStyleSheet(stream.readAll())
qssFile.close()
# print(f"成功加载样式表: {qssPath}")
# else:
# print(f"警告:样式表文件不存在: {qssPath}")
except Exception as e:
print(f"加载样式表失败: {str(e)}")
def closeEvent(self, event):
self.db.close()

@ -21,7 +21,8 @@ from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill
from openpyxl.utils import get_column_letter
from model.ProcedureModel.ProcedureProcessor import StepTableModel
from UI.ProcedureManager.ProcedureTable import *
# from model.ProcedureModel.ProcedureProcessor import StepTableModel
from utils.DBModels.ProcedureModel import DatabaseManager
from utils import Globals
@ -48,6 +49,114 @@ class StepExecutor(QWidget):
self.initUi(testSteps)
self.initExecutionState()
self.initTimers()
self.loadStylesheet()
def setupButtonIcon(self, button, icon_name, color):
"""设置按钮图标,支持不同状态的颜色变化"""
# 正常状态图标
normal_icon = qta.icon(icon_name, color=color)
# 悬停状态图标(稍微亮一些)
hover_color = self.lightenColor(color, 0.2)
hover_icon = qta.icon(icon_name, color=hover_color)
# 按下状态图标(稍微暗一些)
pressed_color = self.darkenColor(color, 0.2)
pressed_icon = qta.icon(icon_name, color=pressed_color)
# 设置图标
button.setIcon(normal_icon)
# 存储不同状态的图标,用于状态切换
button._normal_icon = normal_icon
button._hover_icon = hover_icon
button._pressed_icon = pressed_icon
# 连接事件
button.enterEvent = lambda event: self.onButtonEnter(button, event)
button.leaveEvent = lambda event: self.onButtonLeave(button, event)
button.mousePressEvent = lambda event: self.onButtonPress(button, event)
button.mouseReleaseEvent = lambda event: self.onButtonRelease(button, event)
def lightenColor(self, color, factor):
"""使颜色变亮"""
if color.startswith('#'):
# 十六进制颜色
r = int(color[1:3], 16)
g = int(color[3:5], 16)
b = int(color[5:7], 16)
r = min(255, int(r + (255 - r) * factor))
g = min(255, int(g + (255 - g) * factor))
b = min(255, int(b + (255 - b) * factor))
return f"#{r:02x}{g:02x}{b:02x}"
return color
def darkenColor(self, color, factor):
"""使颜色变暗"""
if color.startswith('#'):
# 十六进制颜色
r = int(color[1:3], 16)
g = int(color[3:5], 16)
b = int(color[5:7], 16)
r = max(0, int(r * (1 - factor)))
g = max(0, int(g * (1 - factor)))
b = max(0, int(b * (1 - factor)))
return f"#{r:02x}{g:02x}{b:02x}"
return color
def onButtonEnter(self, button, event):
"""按钮鼠标进入事件"""
if hasattr(button, '_hover_icon'):
button.setIcon(button._hover_icon)
# 调用原始的enterEvent
QPushButton.enterEvent(button, event)
def onButtonLeave(self, button, event):
"""按钮鼠标离开事件"""
if hasattr(button, '_normal_icon'):
button.setIcon(button._normal_icon)
# 调用原始的leaveEvent
QPushButton.leaveEvent(button, event)
def onButtonPress(self, button, event):
"""按钮鼠标按下事件"""
if hasattr(button, '_pressed_icon'):
button.setIcon(button._pressed_icon)
# 调用原始的mousePressEvent
QPushButton.mousePressEvent(button, event)
def onButtonRelease(self, button, event):
"""按钮鼠标释放事件"""
# 检查鼠标是否还在按钮上
if button.rect().contains(event.pos()):
if hasattr(button, '_hover_icon'):
button.setIcon(button._hover_icon)
else:
if hasattr(button, '_normal_icon'):
button.setIcon(button._normal_icon)
# 调用原始的mouseReleaseEvent
QPushButton.mouseReleaseEvent(button, event)
def loadStylesheet(self):
"""加载样式表"""
try:
qssPath = "Static/StepExecutor.qss"
qssFile = QFile(qssPath)
if qssFile.exists():
qssFile.open(QFile.ReadOnly | QFile.Text)
stream = QTextStream(qssFile)
stream.setCodec("UTF-8")
self.setStyleSheet(stream.readAll())
qssFile.close()
print(f"✅ StepExecutor成功加载样式表: {qssPath}")
else:
print(f"⚠️ StepExecutor样式表文件不存在: {qssPath}")
except Exception as e:
print(f"❌ StepExecutor加载样式表失败: {str(e)}")
def initExecutionState(self):
"""初始化执行状态"""
@ -78,7 +187,10 @@ class StepExecutor(QWidget):
layout = QVBoxLayout()
self.createInfoSection(layout)
self.createTableSection(layout, testSteps)
# self.createTableSection(layout, testSteps)
self.tableView = ProcedureTabel(testSteps, self)
layout.addWidget(self.tableView)
self.tableModel = self.tableView.tableModel
self.createControlSection(layout)
self.createSettingsSection(layout)
layout.setSpacing(0)
@ -255,134 +367,62 @@ class StepExecutor(QWidget):
return groupWidget
def createTableSection(self, layout, testSteps):
"""创建表格区域"""
try:
self.tableModel = StepTableModel(testSteps)
self.tableView = QTableView()
self.tableView.setModel(self.tableModel)
self.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
self.tableView.customContextMenuRequested.connect(self.showContextMenu)
# 设置表格编辑行为
self.tableView.setEditTriggers(QTableView.DoubleClicked)
self.tableView.setSelectionBehavior(QTableView.SelectRows)
# 设置表格显示优化
self.tableView.setAlternatingRowColors(False) # 交替行颜色
self.tableView.setShowGrid(True) # 显示网格线
self.tableView.setGridStyle(Qt.SolidLine) # 实线网格
self.tableView.setSortingEnabled(False) # 禁用排序避免干扰执行顺序
# 设置最小行高
self.tableView.verticalHeader().setMinimumSectionSize(30)
self.setupTableHeaders()
# 初始调整所有行高
self.adjustAllRowHeights()
layout.addWidget(QLabel("测试步骤:"))
layout.addWidget(self.tableView)
except Exception as e:
print(f"创建表格区域时出错: {e}")
import traceback
traceback.print_exc()
# 如果出错,至少创建一个空的表格
self.tableView = QTableView()
layout.addWidget(QLabel("测试步骤:"))
layout.addWidget(self.tableView)
def setupTableHeaders(self):
"""设置表格头部"""
try:
header = self.tableView.horizontalHeader()
if header:
# 设置表格自动调整行高
self.tableView.verticalHeader().setDefaultSectionSize(35)
self.tableView.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
# 设置列宽模式 - 让表格填充满整个宽度
header.setStretchLastSection(True) # 让最后一列自动拉伸填充剩余空间
# 设置各列的调整模式
header.setSectionResizeMode(0, QHeaderView.Fixed) # 步骤ID - 固定宽度
header.setSectionResizeMode(1, QHeaderView.Stretch) # 步骤描述 - 拉伸
header.setSectionResizeMode(2, QHeaderView.Fixed) # 操作类型 - 固定宽度
header.setSectionResizeMode(3, QHeaderView.Fixed) # 执行时间 - 固定宽度
header.setSectionResizeMode(4, QHeaderView.Fixed) # 执行结果 - 固定宽度
header.setSectionResizeMode(5, QHeaderView.Stretch) # 详细结果 - 拉伸
header.setSectionResizeMode(6, QHeaderView.Stretch) # 备注 - 拉伸
# 设置固定列的宽度
self.tableView.setColumnWidth(0, 80) # 步骤ID
self.tableView.setColumnWidth(2, 120) # 操作类型
self.tableView.setColumnWidth(3, 160) # 执行时间
self.tableView.setColumnWidth(4, 80) # 执行结果
# 设置拉伸列的最小宽度
header.setMinimumSectionSize(100) # 设置所有列的最小宽度
# 启用文本换行
self.tableView.setWordWrap(True)
except Exception as e:
print(f"设置表格头部时出错: {e}")
def createControlSection(self, layout):
"""创建控制按钮区域"""
controlLayout = QHBoxLayout()
controlLayout.setSpacing(12)
controlLayout.setContentsMargins(8, 8, 8, 8)
# 开始自动执行按钮 - 绿色主题
self.autoButton = QPushButton("开始自动执行")
self.autoButton.setObjectName("autoExecuteBtn")
self.autoButton.setIconSize(QSize(18, 18))
self.autoButton.clicked.connect(self.startAutoExecute)
self.autoButton.setIcon(qta.icon('fa5s.play', color='white'))
self.autoButton.setToolTip("开始自动执行整个规程流程\n将按照设定的轮次和间隔时间自动执行所有步骤")
self.setupButtonIcon(self.autoButton, 'fa5s.play', '#047857')
# 停止自动执行按钮 - 红色主题
self.stopButton = QPushButton("停止自动执行")
self.stopButton.setObjectName("stopExecuteBtn")
self.stopButton.setIconSize(QSize(18, 18))
self.stopButton.clicked.connect(self.stopAutoExecute)
self.stopButton.setEnabled(False)
self.stopButton.setIcon(qta.icon('fa5s.stop', color='white'))
self.stopButton.setToolTip("停止当前正在执行的自动流程\n可以随时中断执行过程")
self.setupButtonIcon(self.stopButton, 'fa5s.stop', '#B91C1C')
# 执行下一步按钮 - 蓝色主题
self.nextButton = QPushButton("执行下一步")
self.nextButton.setObjectName("nextStepBtn")
self.nextButton.setIconSize(QSize(18, 18))
self.nextButton.clicked.connect(self.executeNextStep)
self.nextButton.setIcon(qta.icon('fa5s.step-forward', color='white'))
self.nextButton.setToolTip("手动执行下一个步骤\n用于单步调试和手动控制执行过程")
self.setupButtonIcon(self.nextButton, 'fa5s.step-forward', '#1D4ED8')
# 完全重置按钮 - 橙色主题
self.resetButton = QPushButton("完全重置")
self.resetButton.setObjectName("resetExecuteBtn")
self.resetButton.setIconSize(QSize(18, 18))
self.resetButton.clicked.connect(self.resetExecution)
self.resetButton.setIcon(qta.icon('fa5s.redo', color='white'))
self.resetButton.setToolTip("重置所有执行状态\n清除所有步骤的执行结果和进度")
self.setupButtonIcon(self.resetButton, 'fa5s.redo', '#D97706')
# 生成报告按钮 - 紫色主题
self.exportButton = QPushButton("生成报告")
self.exportButton.setObjectName("exportReportBtn")
self.exportButton.setIconSize(QSize(18, 18))
self.exportButton.clicked.connect(self.onExportReportClicked)
self.exportButton.setIcon(qta.icon('fa5s.file-alt', color='white'))
self.exportButton.setToolTip("生成执行报告\n导出详细的执行结果和统计数据")
self.setupButtonIcon(self.exportButton, 'fa5s.file-alt', '#7C3AED')
# 创建按钮分组布局
# 第一组:执行控制按钮
executionGroup = QHBoxLayout()
executionGroup.addWidget(self.autoButton)
executionGroup.addWidget(self.stopButton)
executionGroup.addWidget(self.nextButton)
# executionGroup.addStretch() # 添加弹性空间
# 第二组:管理按钮
# managementGroup = QHBoxLayout()
# managementGroup.addStretch() # 添加弹性空间
executionGroup.addWidget(self.resetButton)
executionGroup.addWidget(self.exportButton)
controlLayout.setSpacing(15)
# 将两组按钮添加到主布局
controlLayout.addLayout(executionGroup)
# controlLayout.addLayout(managementGroup)
# 添加按钮到布局
controlLayout.addWidget(self.autoButton)
controlLayout.addWidget(self.stopButton)
controlLayout.addWidget(self.nextButton)
controlLayout.addWidget(self.resetButton)
controlLayout.addWidget(self.exportButton)
# controlLayout.addStretch() # 添加弹性空间,将按钮推到左侧
layout.addLayout(controlLayout)
@ -649,62 +689,7 @@ class StepExecutor(QWidget):
step.get('result') and
'失败' not in step.get('result', ''))
def adjustRowHeight(self, row):
"""调整指定行的高度以适应内容"""
try:
# 获取该行的内容
stepInfo = self.tableModel.getStepInfo(row)
if not stepInfo:
return
# 计算需要的行高确保所有值都不为None
description = stepInfo.get('description') or ''
result = stepInfo.get('result') or ''
note = stepInfo.get('note') or ''
# 确保所有值都是字符串类型
if not isinstance(description, str):
description = str(description) if description is not None else ''
if not isinstance(result, str):
result = str(result) if result is not None else ''
if not isinstance(note, str):
note = str(note) if note is not None else ''
# 基于最长文本计算行高
max_text_length = max(len(description), len(result), len(note))
# 基础行高
base_height = 35
# 根据文本长度动态调整
if max_text_length > 100:
height = base_height + (max_text_length // 50) * 15
elif max_text_length > 50:
height = base_height + 15
else:
height = base_height
# 设置行高最大不超过150像素
height = min(height, 150)
self.tableView.setRowHeight(row, height)
except Exception as e:
print(f"调整行高时出错: {e}")
def adjustAllRowHeights(self):
"""调整所有行的高度"""
try:
if not hasattr(self, 'tableModel') or self.tableModel is None:
return
row_count = self.tableModel.rowCount()
if row_count <= 0:
return
for row in range(row_count):
self.adjustRowHeight(row)
except Exception as e:
print(f"调整所有行高时出错: {e}")
def finishExecution(self):
"""完成执行"""
@ -793,7 +778,7 @@ class StepExecutor(QWidget):
success = self.tableModel.updateStepResult(row, result, datetime.now())
# 更新表格行高以适应内容
self.adjustRowHeight(row)
self.tableView.adjustRowHeight(row)
return result

@ -85,205 +85,4 @@ class ExcelParser:
"测试步骤": testSteps
}
class StepTableModel(QAbstractTableModel):
columns = ['序号', '实验步骤','操作类型', '执行时间', '是否与预期一致', '实际结果', '备注']
def __init__(self, testSteps):
super().__init__()
self.stepData = []
self.stepIndex = 0
for mainStep in testSteps:
self.stepData.append({
'id': self.stepIndex,
'isMain': True,
'stepId': mainStep['步骤ID'],
'description': mainStep['步骤描述'],
'executed': False,
'stepType': mainStep['操作类型'],
'time': None,
'result': None,
'note': mainStep.get('备注', '') # 修复:从主步骤的备注字段读取
})
self.stepIndex += 1
for subStep in mainStep['子步骤']:
self.stepData.append({
'id': self.stepIndex,
'isMain': False,
'stepId': f"{mainStep['步骤ID']}{subStep['序号']}",
'description': subStep['操作'],
'stepType': subStep['操作类型'],
'executed': False,
'time': None,
'result': None,
'note': subStep['备注'] # 新增从Excel解析的备注字段
})
self.stepIndex += 1
def rowCount(self, parent=QModelIndex()):
return len(self.stepData)
def columnCount(self, parent=QModelIndex()):
return len(self.columns)
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
row = index.row()
col = index.column()
step = self.stepData[row]
if role == Qt.DisplayRole:
if col == 0:
return step['stepId'] or ''
elif col == 1:
return step['description'] or ''
elif col == 2:
return step['stepType'] or ''
elif col == 3:
return step['time'].strftime("%Y-%m-%d %H:%M:%S") if step['time'] else ''
elif col == 4:
# print(step['result'])
if step['executed']:
if '失败' in step['result']:
return ''
else:
return ''
elif col == 5:
# print(step['result'])
return step['result'] or ''
elif col == 6:
return step['note'] if step['note'] else ''
elif role == Qt.BackgroundRole:
if step['executed']:
if '失败' in step['result']:
return QBrush(QColor(255, 182, 193))
else:
return QBrush(QColor(144, 238, 144))
elif step['isMain']:
return QBrush(QColor(220, 220, 220))
elif role == Qt.TextAlignmentRole:
if col in [0, 2, 3, 4]: # 步骤ID、操作类型、执行时间、执行结果居中对齐
return Qt.AlignCenter
else: # 其他列左对齐并顶部对齐
return Qt.AlignLeft | Qt.AlignTop
elif role == Qt.FontRole and step['isMain']:
font = QFont()
font.setBold(True)
return font
# 新增:支持自动换行
elif role == Qt.TextAlignmentRole:
if col in [1, 5]: # 描述和备注列
return Qt.AlignLeft | Qt.AlignVCenter
elif role == Qt.TextWordWrap:
if col in [1, 5]:
return True
return None
def flags(self, index):
"""返回单元格标志"""
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
return flags
def headerData(self, section, orientation, role):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.columns[section]
return None
def getStepInfo(self, row):
if 0 <= row < len(self.stepData):
return self.stepData[row]
return None
def getFullStepInfo(self, row):
"""获取完整的步骤信息"""
if 0 <= row < len(self.stepData):
return self.stepData[row]
return None
def updateStepResult(self, row, result, time):
if 0 <= row < len(self.stepData):
# print(result)
self.stepData[row]['executed'] = True
self.stepData[row]['time'] = time
self.stepData[row]['result'] = result
self.dataChanged.emit(self.index(row, 0),
self.index(row, self.columnCount()-1),
[Qt.DisplayRole, Qt.BackgroundRole])
return True
return False
def resetExecutionState(self):
"""只重置执行状态(颜色),不清除时间和结果"""
for step in self.stepData:
step['executed'] = False
step['result'] = None
self.dataChanged.emit(self.index(0, 0),
self.index(self.rowCount()-1, self.columnCount()-1),
[Qt.BackgroundRole])
def resetAll(self):
"""完全重置所有状态(包括时间和结果)"""
for step in self.stepData:
step.update({
'executed': False,
'time': None,
'result': None
})
self.dataChanged.emit(self.index(0, 0),
self.index(self.rowCount()-1, self.columnCount()-1),
[Qt.DisplayRole, Qt.BackgroundRole])
def getTestSteps(self):
"""获取测试步骤数据,用于保存"""
testSteps = []
currentMainStep = None
for step in self.stepData:
if step['isMain']:
# 如果有前一个主步骤,先添加到结果中
if currentMainStep:
testSteps.append(currentMainStep)
# 创建新的主步骤
currentMainStep = {
'步骤ID': step['stepId'],
'步骤描述': step['description'],
'操作类型': step['stepType'],
'预期结果': '',
'子步骤': []
}
else:
# 子步骤
if currentMainStep:
# 从步骤ID中提取序号
stepId = step['stepId']
if currentMainStep['步骤ID'] in stepId:
subStepId = stepId.replace(currentMainStep['步骤ID'], '')
else:
subStepId = stepId
subStep = {
'序号': subStepId,
'操作': step['description'],
'操作类型': step['stepType'],
'预期结果': '',
'实际结果': step.get('result', ''),
'一致性': '' if step.get('result') and '失败' not in step.get('result', '') else '',
'测试时间': step.get('time', ''),
'备注': step.get('note', '')
}
currentMainStep['子步骤'].append(subStep)
# 添加最后一个主步骤
if currentMainStep:
testSteps.append(currentMainStep)
return testSteps

Loading…
Cancel
Save