|
|
|
@ -7,7 +7,7 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QTableView,
|
|
|
|
|
QLabel, QCheckBox, QSpinBox, QMenu, QFileDialog,
|
|
|
|
|
QTabWidget, QTabBar, QListWidget, QListWidgetItem, QDialog,
|
|
|
|
|
QLineEdit, QFormLayout, QDialogButtonBox, QMessageBox,
|
|
|
|
|
QHeaderView, QToolBar, QAction, QStatusBar, QComboBox, QSplitter, QAbstractItemView)
|
|
|
|
|
QHeaderView, QToolBar, QAction, QStatusBar, QComboBox, QSplitter, QAbstractItemView, QDoubleSpinBox)
|
|
|
|
|
from docx import Document
|
|
|
|
|
from docx.shared import Pt, RGBColor
|
|
|
|
|
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
|
|
|
@ -18,12 +18,15 @@ from datetime import datetime
|
|
|
|
|
# 导入其他模块
|
|
|
|
|
from model.ProcedureModel.ProcedureProcessor import ExcelParser, StepTableModel
|
|
|
|
|
from utils.DBModels.ProcedureModel import DatabaseManager
|
|
|
|
|
from utils import Globals
|
|
|
|
|
# 修改导入路径
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class StepExecutor(QWidget):
|
|
|
|
|
# 添加标签锁定信号
|
|
|
|
|
tabLockRequired = pyqtSignal(bool)
|
|
|
|
|
# 添加执行完成信号
|
|
|
|
|
executionFinished = pyqtSignal(object) # 发送执行器实例
|
|
|
|
|
|
|
|
|
|
def __init__(self, procedureData, procedureId, dbManager):
|
|
|
|
|
super().__init__()
|
|
|
|
@ -44,6 +47,14 @@ class StepExecutor(QWidget):
|
|
|
|
|
self.isFirstRun = True # 新增标志位,用于区分首次执行
|
|
|
|
|
self.stepResults = [] # 新增:存储所有步骤执行结果的列表
|
|
|
|
|
|
|
|
|
|
# 新增:倒计时相关变量
|
|
|
|
|
self.countdownTimer = QTimer()
|
|
|
|
|
self.countdownTimer.timeout.connect(self.updateCountdown)
|
|
|
|
|
self.remainingTime = 0 # 当前轮次剩余时间(秒)
|
|
|
|
|
self.totalSteps = 0 # 当前轮次总步骤数
|
|
|
|
|
|
|
|
|
|
self.protocolManage = Globals.getValue("protocolManage")
|
|
|
|
|
|
|
|
|
|
def initUi(self, testSteps):
|
|
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
|
|
|
@ -65,11 +76,18 @@ class StepExecutor(QWidget):
|
|
|
|
|
self.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
|
|
|
self.tableView.customContextMenuRequested.connect(self.showContextMenu)
|
|
|
|
|
|
|
|
|
|
self.tableView.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
|
|
|
|
|
self.tableView.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
|
|
|
|
|
self.tableView.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)
|
|
|
|
|
self.tableView.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeToContents)
|
|
|
|
|
self.tableView.horizontalHeader().setSectionResizeMode(5, QHeaderView.Stretch) # 新增:备注列自适应宽度
|
|
|
|
|
# QHeaderView保护
|
|
|
|
|
hh = self.tableView.horizontalHeader()
|
|
|
|
|
if hh:
|
|
|
|
|
hh.setSectionResizeMode(0, QHeaderView.ResizeToContents)
|
|
|
|
|
hh.setSectionResizeMode(1, QHeaderView.Stretch)
|
|
|
|
|
hh.setSectionResizeMode(2, QHeaderView.ResizeToContents)
|
|
|
|
|
hh.setSectionResizeMode(3, QHeaderView.ResizeToContents)
|
|
|
|
|
hh.setSectionResizeMode(5, QHeaderView.Stretch)
|
|
|
|
|
vh = self.tableView.verticalHeader()
|
|
|
|
|
if vh:
|
|
|
|
|
self.tableView.setWordWrap(True)
|
|
|
|
|
vh.setSectionResizeMode(QHeaderView.ResizeToContents)
|
|
|
|
|
|
|
|
|
|
layout.addWidget(QLabel("测试步骤:"))
|
|
|
|
|
layout.addWidget(self.tableView)
|
|
|
|
@ -95,7 +113,7 @@ class StepExecutor(QWidget):
|
|
|
|
|
self.resetButton.setIcon(qta.icon('fa5s.redo', color='orange'))
|
|
|
|
|
|
|
|
|
|
self.exportButton = QPushButton(" 生成报告")
|
|
|
|
|
self.exportButton.clicked.connect(self.generateReport)
|
|
|
|
|
self.exportButton.clicked.connect(self.onExportReportClicked)
|
|
|
|
|
self.exportButton.setIcon(qta.icon('fa5s.file-alt', color='purple'))
|
|
|
|
|
|
|
|
|
|
control_layout.addWidget(self.autoButton)
|
|
|
|
@ -115,6 +133,27 @@ class StepExecutor(QWidget):
|
|
|
|
|
|
|
|
|
|
self.infiniteCheckbox = QCheckBox("无限循环")
|
|
|
|
|
cycle_layout.addWidget(self.infiniteCheckbox)
|
|
|
|
|
|
|
|
|
|
# 新增:状态显示标签
|
|
|
|
|
self.statusLabel = QLabel("就绪")
|
|
|
|
|
self.statusLabel.setStyleSheet("color: blue; font-weight: bold;")
|
|
|
|
|
cycle_layout.addWidget(self.statusLabel)
|
|
|
|
|
|
|
|
|
|
# 新增:倒计时显示标签
|
|
|
|
|
self.countdownLabel = QLabel("")
|
|
|
|
|
self.countdownLabel.setStyleSheet("color: red; font-weight: bold; font-size: 14px;")
|
|
|
|
|
cycle_layout.addWidget(self.countdownLabel)
|
|
|
|
|
|
|
|
|
|
# 新增:步骤间隔时间设置
|
|
|
|
|
cycle_layout.addWidget(QLabel("步骤间隔(秒):"))
|
|
|
|
|
self.stepIntervalSpin = QDoubleSpinBox()
|
|
|
|
|
self.stepIntervalSpin.setRange(0, 60.0)
|
|
|
|
|
self.stepIntervalSpin.setValue(1)
|
|
|
|
|
self.stepIntervalSpin.setDecimals(2) # 支持一位小数
|
|
|
|
|
self.stepIntervalSpin.setSingleStep(0.1) # 每次调整0.1秒
|
|
|
|
|
self.stepIntervalSpin.setToolTip("设置步骤执行计时器的间隔时间(秒)")
|
|
|
|
|
cycle_layout.addWidget(self.stepIntervalSpin)
|
|
|
|
|
|
|
|
|
|
cycle_layout.addStretch()
|
|
|
|
|
|
|
|
|
|
# 将所有布局添加到主布局
|
|
|
|
@ -135,17 +174,20 @@ class StepExecutor(QWidget):
|
|
|
|
|
self.toolbar.addSeparator()
|
|
|
|
|
self.toolbar.addAction(qta.icon('fa5s.redo', color='orange'), "重置", self.resetExecution)
|
|
|
|
|
self.toolbar.addSeparator()
|
|
|
|
|
self.toolbar.addAction(qta.icon('fa5s.file-alt', color='purple'), "生成报告", self.generateReport)
|
|
|
|
|
self.toolbar.addAction(qta.icon('fa5s.file-alt', color='purple'), "生成报告", self.onExportReportClicked)
|
|
|
|
|
|
|
|
|
|
def showContextMenu(self, pos):
|
|
|
|
|
index = self.tableView.indexAt(pos)
|
|
|
|
|
if index.isValid():
|
|
|
|
|
menu = QMenu()
|
|
|
|
|
jumpAction = menu.addAction("从该步骤开始执行")
|
|
|
|
|
jumpAction.triggered.connect(lambda: self.jumpExecute(index.row()))
|
|
|
|
|
if jumpAction:
|
|
|
|
|
jumpAction.triggered.connect(lambda: self.jumpExecute(index.row()))
|
|
|
|
|
detailAction = menu.addAction("查看步骤详情")
|
|
|
|
|
detailAction.triggered.connect(lambda: self.showStepDetail(index.row()))
|
|
|
|
|
menu.exec_(self.tableView.viewport().mapToGlobal(pos))
|
|
|
|
|
if detailAction:
|
|
|
|
|
detailAction.triggered.connect(lambda: self.showStepDetail(index.row()))
|
|
|
|
|
if hasattr(self.tableView, 'viewport') and hasattr(self.tableView.viewport(), 'mapToGlobal'):
|
|
|
|
|
menu.exec_(self.tableView.viewport().mapToGlobal(pos))
|
|
|
|
|
|
|
|
|
|
def jumpExecute(self, rowIndex):
|
|
|
|
|
if 0 <= rowIndex < self.tableModel.rowCount():
|
|
|
|
@ -171,11 +213,11 @@ class StepExecutor(QWidget):
|
|
|
|
|
form_layout.addRow("步骤描述", QLabel(step_info['description']))
|
|
|
|
|
|
|
|
|
|
if step_info['time']:
|
|
|
|
|
form_layout.addRow("执行时间"); QLabel(step_info['time'].strftime("%Y-%m-%d %H:%M:%S"))
|
|
|
|
|
form_layout.addRow("执行时间", QLabel(step_info['time'].strftime("%Y-%m-%d %H:%M:%S")))
|
|
|
|
|
|
|
|
|
|
if step_info['result'] is not None:
|
|
|
|
|
status = "成功" if step_info['result'] else "失败"
|
|
|
|
|
form_layout.addRow("执行结果"); QLabel(status)
|
|
|
|
|
form_layout.addRow("执行结果", QLabel(status))
|
|
|
|
|
|
|
|
|
|
layout.addLayout(form_layout)
|
|
|
|
|
|
|
|
|
@ -186,6 +228,11 @@ class StepExecutor(QWidget):
|
|
|
|
|
detail_dialog.setLayout(layout)
|
|
|
|
|
detail_dialog.exec_()
|
|
|
|
|
|
|
|
|
|
def updateStatusDisplay(self, message, color="blue"):
|
|
|
|
|
"""更新状态显示"""
|
|
|
|
|
self.statusLabel.setText(message)
|
|
|
|
|
self.statusLabel.setStyleSheet(f"color: {color}; font-weight: bold;")
|
|
|
|
|
|
|
|
|
|
def startAutoExecute(self):
|
|
|
|
|
self.isRunning = True
|
|
|
|
|
self.isActive = True
|
|
|
|
@ -202,10 +249,21 @@ class StepExecutor(QWidget):
|
|
|
|
|
self.stepResults = [] # 重置步骤结果集合
|
|
|
|
|
self.tableModel.resetExecutionState()
|
|
|
|
|
self.isFirstRun = False # 标记已执行过
|
|
|
|
|
# 创建第一个执行记录
|
|
|
|
|
self.createNewExecutionRecord()
|
|
|
|
|
|
|
|
|
|
self.remainingCycles = self.cycleSpin.value()
|
|
|
|
|
self.infiniteCycles = self.infiniteCheckbox.isChecked()
|
|
|
|
|
self.timer.start(1000)
|
|
|
|
|
|
|
|
|
|
# 使用用户设置的步骤间隔时间启动计时器
|
|
|
|
|
stepInterval = int(self.stepIntervalSpin.value() * 1000) # 转换为毫秒
|
|
|
|
|
self.timer.start(stepInterval)
|
|
|
|
|
|
|
|
|
|
# 更新状态显示
|
|
|
|
|
self.updateStatusDisplay(f"开始执行 - 第1轮", "green")
|
|
|
|
|
|
|
|
|
|
# 开始倒计时(在第一个步骤执行前)
|
|
|
|
|
self.startCountdown()
|
|
|
|
|
|
|
|
|
|
def stopAutoExecute(self):
|
|
|
|
|
self.isRunning = False # 清除自动执行状态
|
|
|
|
@ -216,9 +274,28 @@ class StepExecutor(QWidget):
|
|
|
|
|
self.resetButton.setEnabled(True)
|
|
|
|
|
self.exportButton.setEnabled(True)
|
|
|
|
|
# 注意: 这里不重置isActive,因为执行器仍处于激活状态
|
|
|
|
|
# 执行结束时更新完整步骤结果到数据库
|
|
|
|
|
if hasattr(self, 'current_execution_id'):
|
|
|
|
|
# 执行结束时保存当前轮次的步骤结果到数据库
|
|
|
|
|
if hasattr(self, 'current_execution_id') and self.stepResults:
|
|
|
|
|
import json
|
|
|
|
|
try:
|
|
|
|
|
json.dumps(self.stepResults)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print("stepResults不能序列化", self.stepResults)
|
|
|
|
|
raise
|
|
|
|
|
self.dbManager.updateStepResults(self.current_execution_id, self.stepResults)
|
|
|
|
|
print(f"执行停止,当前轮次结果已保存")
|
|
|
|
|
|
|
|
|
|
# 更新状态显示
|
|
|
|
|
self.updateStatusDisplay("执行已停止", "orange")
|
|
|
|
|
|
|
|
|
|
# 停止倒计时
|
|
|
|
|
self.stopCountdown()
|
|
|
|
|
|
|
|
|
|
# 发送执行完成信号
|
|
|
|
|
self.executionFinished.emit(self)
|
|
|
|
|
# 执行完毕后自动解锁标签页
|
|
|
|
|
self.tabLockRequired.emit(False)
|
|
|
|
|
# self.resetExecution() # 自动执行完毕后自动重置
|
|
|
|
|
|
|
|
|
|
def autoExecuteStep(self):
|
|
|
|
|
if self.currentIndex < self.tableModel.rowCount():
|
|
|
|
@ -226,7 +303,21 @@ class StepExecutor(QWidget):
|
|
|
|
|
if step_info and not step_info['isMain']:
|
|
|
|
|
self.executeStep(self.currentIndex)
|
|
|
|
|
self.currentIndex += 1
|
|
|
|
|
|
|
|
|
|
# 步骤执行完成后,更新倒计时
|
|
|
|
|
self.startCountdown()
|
|
|
|
|
else:
|
|
|
|
|
# 当前轮次执行完成,立即存储执行结果
|
|
|
|
|
if hasattr(self, 'current_execution_id') and self.stepResults:
|
|
|
|
|
import json
|
|
|
|
|
try:
|
|
|
|
|
json.dumps(self.stepResults)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print("stepResults不能序列化", self.stepResults)
|
|
|
|
|
raise
|
|
|
|
|
self.dbManager.updateStepResults(self.current_execution_id, self.stepResults)
|
|
|
|
|
print(f"第 {self.getCurrentCycleNumber()} 轮执行完成,结果已存储")
|
|
|
|
|
|
|
|
|
|
if self.infiniteCycles or self.remainingCycles > 0:
|
|
|
|
|
if not self.infiniteCycles:
|
|
|
|
|
self.remainingCycles -= 1
|
|
|
|
@ -235,14 +326,49 @@ class StepExecutor(QWidget):
|
|
|
|
|
|
|
|
|
|
should_continue = self.infiniteCycles or self.remainingCycles > 0
|
|
|
|
|
if should_continue:
|
|
|
|
|
self.tableModel.resetAll()
|
|
|
|
|
if hasattr(self, 'current_execution_id'):
|
|
|
|
|
self.dbManager.updateStepResults(self.current_execution_id, self.stepResults)
|
|
|
|
|
self.timer.start()
|
|
|
|
|
# 开始新一轮执行前,创建新的执行记录
|
|
|
|
|
self.createNewExecutionRecord()
|
|
|
|
|
self.tableModel.resetAll() # 确保每轮自动清除颜色
|
|
|
|
|
self.stepResults = [] # 清空步骤结果,准备新一轮
|
|
|
|
|
|
|
|
|
|
# 更新状态显示
|
|
|
|
|
cycle_num = self.getCurrentCycleNumber()
|
|
|
|
|
self.updateStatusDisplay(f"执行中 - 第{cycle_num}轮", "green")
|
|
|
|
|
|
|
|
|
|
# 重新开始倒计时
|
|
|
|
|
self.startCountdown()
|
|
|
|
|
|
|
|
|
|
# 使用用户设置的步骤间隔时间重新启动计时器
|
|
|
|
|
stepInterval = int(self.stepIntervalSpin.value() * 1000) # 转换为毫秒
|
|
|
|
|
self.timer.start(stepInterval)
|
|
|
|
|
else:
|
|
|
|
|
self.stopAutoExecute()
|
|
|
|
|
|
|
|
|
|
# 发送执行完成信号
|
|
|
|
|
self.executionFinished.emit(self)
|
|
|
|
|
else:
|
|
|
|
|
self.stopAutoExecute()
|
|
|
|
|
|
|
|
|
|
# 发送执行完成信号
|
|
|
|
|
self.executionFinished.emit(self)
|
|
|
|
|
|
|
|
|
|
def createNewExecutionRecord(self):
|
|
|
|
|
"""创建新的执行记录"""
|
|
|
|
|
execution_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
self.current_execution_id = self.dbManager.insertProcedureExecution(
|
|
|
|
|
self.procedureId,
|
|
|
|
|
execution_time
|
|
|
|
|
)
|
|
|
|
|
print(f"创建新的执行记录,ID: {self.current_execution_id}")
|
|
|
|
|
|
|
|
|
|
def getCurrentCycleNumber(self):
|
|
|
|
|
"""获取当前执行的轮次数"""
|
|
|
|
|
if self.infiniteCycles:
|
|
|
|
|
return "无限"
|
|
|
|
|
else:
|
|
|
|
|
total_cycles = self.cycleSpin.value()
|
|
|
|
|
completed_cycles = total_cycles - self.remainingCycles
|
|
|
|
|
return f"{completed_cycles + 1}/{total_cycles}"
|
|
|
|
|
|
|
|
|
|
def executeNextStep(self):
|
|
|
|
|
self.isActive = True
|
|
|
|
@ -259,40 +385,23 @@ class StepExecutor(QWidget):
|
|
|
|
|
stepInfo = self.tableModel.getStepInfo(row)
|
|
|
|
|
if not stepInfo:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
print(f"开始执行步骤 {stepInfo['stepId']}: {stepInfo['description']}")
|
|
|
|
|
|
|
|
|
|
result = self.handleStep(row, stepInfo)
|
|
|
|
|
|
|
|
|
|
# 确保result总是布尔值(避免None导致数据库错误)
|
|
|
|
|
if result is None:
|
|
|
|
|
print(f"警告:步骤 {stepInfo['stepId']} 返回了None结果,设置为False")
|
|
|
|
|
result = False
|
|
|
|
|
|
|
|
|
|
# 存储执行结果到数据库
|
|
|
|
|
execution_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
|
|
|
|
|
# 新增:获取当前执行的ID(每次执行一个唯一的execution_id)
|
|
|
|
|
if not hasattr(self, 'current_execution_id'):
|
|
|
|
|
self.current_execution_id = self.dbManager.insertProcedureExecution(
|
|
|
|
|
self.procedureId,
|
|
|
|
|
execution_time
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 存储执行结果到内存集合
|
|
|
|
|
# 只保存基础类型,避免递归
|
|
|
|
|
step_result = {
|
|
|
|
|
'step_id': stepInfo['stepId'],
|
|
|
|
|
'step_description': stepInfo['description'],
|
|
|
|
|
'step_id': str(stepInfo.get('stepId', '')),
|
|
|
|
|
'step_description': str(stepInfo.get('description', '')),
|
|
|
|
|
'execution_time': execution_time,
|
|
|
|
|
'result': result
|
|
|
|
|
'result': bool(result)
|
|
|
|
|
}
|
|
|
|
|
self.stepResults.append(step_result)
|
|
|
|
|
|
|
|
|
|
# 更新数据库中的步骤结果集合
|
|
|
|
|
self.dbManager.updateStepResults(self.current_execution_id, self.stepResults)
|
|
|
|
|
|
|
|
|
|
if hasattr(self, 'current_execution_id'):
|
|
|
|
|
self.dbManager.updateStepResults(self.current_execution_id, self.stepResults)
|
|
|
|
|
success = self.tableModel.updateStepResult(row, result, datetime.now())
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
def handleStep(self, rowIndex, stepInfo): # 修改参数名
|
|
|
|
@ -302,10 +411,10 @@ class StepExecutor(QWidget):
|
|
|
|
|
print(f"处理步骤 {stepId}: {description}")
|
|
|
|
|
|
|
|
|
|
if "设置" in description:
|
|
|
|
|
return self.performLogin(stepId, description) # 修改方法名
|
|
|
|
|
elif "数据导入" in description:
|
|
|
|
|
return self.performWrite(stepId, description) # 修改方法名
|
|
|
|
|
elif "检查" in description:
|
|
|
|
|
return self.performDataImport(stepId, description) # 修改方法名
|
|
|
|
|
elif "验证" in description:
|
|
|
|
|
elif "接收" in description:
|
|
|
|
|
return self.performValidation(stepId, description) # 修改方法名
|
|
|
|
|
elif "导出" in description:
|
|
|
|
|
return self.performExport(stepId, description) # 修改方法名
|
|
|
|
@ -318,7 +427,7 @@ class StepExecutor(QWidget):
|
|
|
|
|
import random
|
|
|
|
|
return random.random() < 0.9
|
|
|
|
|
|
|
|
|
|
def performLogin(self, stepId, description): # 修改方法名
|
|
|
|
|
def performWrite(self, stepId, description): # 修改方法名
|
|
|
|
|
print(f"执行登录操作 (步骤 {stepId}): {description}")
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
@ -355,136 +464,104 @@ class StepExecutor(QWidget):
|
|
|
|
|
def performSetting(self, stepId, description): # 修改方法名
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def resetExecution(self):
|
|
|
|
|
def resetExecution(self, fromAuto=False):
|
|
|
|
|
self.tableModel.resetAll()
|
|
|
|
|
self.currentIndex = 0
|
|
|
|
|
self.stopAutoExecute()
|
|
|
|
|
if not fromAuto:
|
|
|
|
|
self.stopAutoExecute()
|
|
|
|
|
self.cycleSpin.setValue(1)
|
|
|
|
|
self.infiniteCheckbox.setChecked(False)
|
|
|
|
|
self.isRunning = False
|
|
|
|
|
self.isActive = False
|
|
|
|
|
self.isFirstRun = True # 重置标志位
|
|
|
|
|
self.stepResults = [] # 重置步骤结果集合
|
|
|
|
|
|
|
|
|
|
# 新增:重置当前执行ID
|
|
|
|
|
self.stepResults = [] # 重置步骤结果集合,禁止赋值为tableModel.stepData
|
|
|
|
|
if hasattr(self, 'current_execution_id'):
|
|
|
|
|
del self.current_execution_id
|
|
|
|
|
|
|
|
|
|
# 发送标签解锁信号
|
|
|
|
|
self.tabLockRequired.emit(False)
|
|
|
|
|
self.updateStatusDisplay("已重置", "blue")
|
|
|
|
|
self.resetCountdown()
|
|
|
|
|
|
|
|
|
|
# 修改方法签名,添加file_path参数
|
|
|
|
|
def generateReport(self):
|
|
|
|
|
# 生成规程全名(名称+编号)
|
|
|
|
|
proc_full_name = f"{self.procedureData['规程信息']['规程名称']}({self.procedureData['规程信息']['规程编号']})"
|
|
|
|
|
|
|
|
|
|
# 弹出文件选择对话框
|
|
|
|
|
default_name = f"{proc_full_name}_报告_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx"
|
|
|
|
|
file_path, _ = QFileDialog.getSaveFileName(
|
|
|
|
|
self,
|
|
|
|
|
"保存报告",
|
|
|
|
|
default_name,
|
|
|
|
|
"Word文档 (*.docx)"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 如果用户取消选择,则直接返回
|
|
|
|
|
if not file_path:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
doc = Document()
|
|
|
|
|
|
|
|
|
|
# 生成规程全名(名称+编号)
|
|
|
|
|
proc_full_name = f"{self.procedureData['规程信息']['规程名称']}({self.procedureData['规程信息']['规程编号']})"
|
|
|
|
|
title = doc.add_paragraph(f"{proc_full_name} - 测试报告")
|
|
|
|
|
title.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
|
|
|
|
title.runs[0].font.size = Pt(18)
|
|
|
|
|
title.runs[0].bold = True
|
|
|
|
|
|
|
|
|
|
info = doc.add_paragraph()
|
|
|
|
|
info.add_run("规程信息:\n").bold = True
|
|
|
|
|
info.add_run(f"规程编号: {self.procedureData['规程信息']['规程编号']}\n")
|
|
|
|
|
info.add_run(f"规程类型: {self.procedureData['规程信息']['规程类型']}\n")
|
|
|
|
|
|
|
|
|
|
case_info = doc.add_paragraph()
|
|
|
|
|
case_info.add_run("测试用例信息:\n").bold = True
|
|
|
|
|
case_info.add_run(f"测试用例: {self.procedureData['测试用例信息']['测试用例']}\n")
|
|
|
|
|
case_info.add_run(f"用例编号: {self.procedureData['测试用例信息']['用例编号']}\n")
|
|
|
|
|
case_info.add_run(f"工况描述: {self.procedureData['测试用例信息']['工况描述']}\n")
|
|
|
|
|
|
|
|
|
|
stats = doc.add_paragraph()
|
|
|
|
|
stats.add_run("执行统计:\n").bold = True
|
|
|
|
|
total = self.tableModel.rowCount()
|
|
|
|
|
success = sum(1 for s in self.tableModel.stepData if s['result'])
|
|
|
|
|
failure = total - success
|
|
|
|
|
stats.add_run(f"总步骤数: {total}\n成功数: {success}\n失败数: {failure}\n")
|
|
|
|
|
|
|
|
|
|
# 优化步骤表格展示
|
|
|
|
|
steps_table = doc.add_table(rows=1, cols=6)
|
|
|
|
|
steps_table.style = 'Table Grid' # 添加表格边框样式
|
|
|
|
|
hdr_cells = steps_table.rows[0].cells
|
|
|
|
|
|
|
|
|
|
# 设置表头
|
|
|
|
|
headers = ['步骤ID', '步骤类型', '步骤描述', '执行时间', '执行结果', '状态']
|
|
|
|
|
for i, header in enumerate(headers):
|
|
|
|
|
hdr_cells[i].text = header
|
|
|
|
|
# 设置表头样式(加粗居中)
|
|
|
|
|
for paragraph in hdr_cells[i].paragraphs:
|
|
|
|
|
paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
|
|
|
|
run = paragraph.runs[0]
|
|
|
|
|
run.font.bold = True
|
|
|
|
|
|
|
|
|
|
# 填充表格数据
|
|
|
|
|
for step in self.tableModel.stepData:
|
|
|
|
|
row_cells = steps_table.add_row().cells
|
|
|
|
|
|
|
|
|
|
# 步骤ID
|
|
|
|
|
row_cells[0].text = step['stepId']
|
|
|
|
|
|
|
|
|
|
# 步骤类型
|
|
|
|
|
step_type = "主步骤" if step['isMain'] else "子步骤"
|
|
|
|
|
row_cells[1].text = step_type
|
|
|
|
|
|
|
|
|
|
# 步骤描述
|
|
|
|
|
row_cells[2].text = step['description']
|
|
|
|
|
|
|
|
|
|
# 执行时间
|
|
|
|
|
time_text = step['time'].strftime("%Y-%m-%d %H:%M:%S") if step['time'] else 'N/A'
|
|
|
|
|
row_cells[3].text = time_text
|
|
|
|
|
def onExportReportClicked(self):
|
|
|
|
|
self.generateReport()
|
|
|
|
|
|
|
|
|
|
def startCountdown(self):
|
|
|
|
|
"""开始倒计时"""
|
|
|
|
|
# 计算当前轮次的总步骤数(只计算子步骤)
|
|
|
|
|
self.totalSteps = sum(1 for step in self.tableModel.stepData if not step['isMain'])
|
|
|
|
|
|
|
|
|
|
# 计算剩余步骤数(从当前索引开始到结束)
|
|
|
|
|
remainingSteps = 0
|
|
|
|
|
for i in range(self.currentIndex, len(self.tableModel.stepData)):
|
|
|
|
|
if not self.tableModel.stepData[i]['isMain']:
|
|
|
|
|
remainingSteps += 1
|
|
|
|
|
|
|
|
|
|
# 获取步骤间隔时间
|
|
|
|
|
stepInterval = self.stepIntervalSpin.value()
|
|
|
|
|
|
|
|
|
|
# 计算当前轮次的剩余时间(剩余步骤数 * 步骤间隔时间)
|
|
|
|
|
if remainingSteps > 0:
|
|
|
|
|
self.remainingTime = int(remainingSteps * stepInterval) # 转换为整数秒
|
|
|
|
|
self.countdownTimer.start(1000) # 每秒更新一次
|
|
|
|
|
self.updateCountdown()
|
|
|
|
|
else:
|
|
|
|
|
# 如果没有剩余步骤,清空显示
|
|
|
|
|
self.countdownLabel.setText("")
|
|
|
|
|
|
|
|
|
|
def stopCountdown(self):
|
|
|
|
|
"""停止倒计时"""
|
|
|
|
|
self.countdownTimer.stop()
|
|
|
|
|
self.countdownLabel.setText("")
|
|
|
|
|
|
|
|
|
|
def updateCountdown(self):
|
|
|
|
|
"""更新倒计时显示"""
|
|
|
|
|
if self.remainingTime > 0:
|
|
|
|
|
minutes = self.remainingTime // 60
|
|
|
|
|
seconds = self.remainingTime % 60
|
|
|
|
|
|
|
|
|
|
# 执行结果(带颜色标记)
|
|
|
|
|
result_cell = row_cells[4]
|
|
|
|
|
if step['result'] is True:
|
|
|
|
|
result_text = '成功'
|
|
|
|
|
# 确保单元格有run对象
|
|
|
|
|
if not result_cell.paragraphs[0].runs:
|
|
|
|
|
result_cell.paragraphs[0].add_run(result_text)
|
|
|
|
|
result_cell.paragraphs[0].runs[0].font.color.rgb = RGBColor(0, 128, 0) # 绿色
|
|
|
|
|
elif step['result'] is False:
|
|
|
|
|
result_text = '失败'
|
|
|
|
|
# 确保单元格有run对象
|
|
|
|
|
if not result_cell.paragraphs[0].runs:
|
|
|
|
|
result_cell.paragraphs[0].add_run(result_text)
|
|
|
|
|
result_cell.paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 0, 0) # 红色
|
|
|
|
|
if minutes > 0:
|
|
|
|
|
countdown_text = f"当前轮次剩余: {minutes:02d}:{seconds:02d}"
|
|
|
|
|
else:
|
|
|
|
|
result_text = '未执行'
|
|
|
|
|
result_cell.text = result_text
|
|
|
|
|
countdown_text = f"当前轮次剩余: {seconds}秒"
|
|
|
|
|
|
|
|
|
|
# 状态
|
|
|
|
|
status = '已执行' if step['executed'] else '未执行'
|
|
|
|
|
row_cells[5].text = status
|
|
|
|
|
|
|
|
|
|
# 设置表格自动适应宽度
|
|
|
|
|
for col in steps_table.columns:
|
|
|
|
|
col.width = doc.sections[0].page_width // len(headers)
|
|
|
|
|
|
|
|
|
|
# 设置默认文件名(包含规程全名)
|
|
|
|
|
if not file_path:
|
|
|
|
|
filename = f"{proc_full_name}_报告_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx"
|
|
|
|
|
# 根据剩余时间调整颜色
|
|
|
|
|
if self.remainingTime <= 10:
|
|
|
|
|
self.countdownLabel.setStyleSheet("color: red; font-weight: bold; font-size: 14px;")
|
|
|
|
|
elif self.remainingTime <= 30:
|
|
|
|
|
self.countdownLabel.setStyleSheet("color: orange; font-weight: bold; font-size: 14px;")
|
|
|
|
|
else:
|
|
|
|
|
self.countdownLabel.setStyleSheet("color: blue; font-weight: bold; font-size: 14px;")
|
|
|
|
|
|
|
|
|
|
self.countdownLabel.setText(countdown_text)
|
|
|
|
|
self.remainingTime -= 1
|
|
|
|
|
else:
|
|
|
|
|
filename = file_path
|
|
|
|
|
|
|
|
|
|
doc.save(filename)
|
|
|
|
|
|
|
|
|
|
# 确保导出成功时显示提示信息
|
|
|
|
|
QMessageBox.information(self, "报告生成", f"报告已生成: {filename}")
|
|
|
|
|
return filename
|
|
|
|
|
self.countdownLabel.setText("当前轮次完成")
|
|
|
|
|
self.countdownLabel.setStyleSheet("color: green; font-weight: bold; font-size: 14px;")
|
|
|
|
|
self.countdownTimer.stop()
|
|
|
|
|
|
|
|
|
|
def updateCycleCountdown(self):
|
|
|
|
|
"""更新轮次剩余时间倒计时 - 已废弃,保留兼容性"""
|
|
|
|
|
self.updateCountdown()
|
|
|
|
|
|
|
|
|
|
def resetCountdown(self):
|
|
|
|
|
"""重置倒计时"""
|
|
|
|
|
self.stopCountdown()
|
|
|
|
|
self.remainingTime = 0
|
|
|
|
|
self.totalSteps = 0
|
|
|
|
|
|
|
|
|
|
def isExecutionCompleted(self):
|
|
|
|
|
"""检查规程是否执行完成"""
|
|
|
|
|
# 检查是否所有步骤都执行完成
|
|
|
|
|
return self.currentIndex >= self.tableModel.rowCount()
|
|
|
|
|
|
|
|
|
|
def getExecutionProgress(self):
|
|
|
|
|
"""获取执行进度"""
|
|
|
|
|
total_steps = self.tableModel.rowCount()
|
|
|
|
|
completed_steps = self.currentIndex
|
|
|
|
|
return completed_steps, total_steps
|
|
|
|
|
|
|
|
|
|
def showEvent(self, event):
|
|
|
|
|
super().showEvent(event)
|
|
|
|
|
# 只在第一次显示时调整行高
|
|
|
|
|
if not hasattr(self, '_hasResizedRows'):
|
|
|
|
|
self.tableView.resizeRowsToContents()
|
|
|
|
|
self._hasResizedRows = True
|