|
|
|
|
import sys
|
|
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
from PyQt5.QtCore import Qt, QTimer, QSize, pyqtSignal, QFile, QTextStream
|
|
|
|
|
from PyQt5.QtGui import QBrush, QColor, QStandardItemModel, QStandardItem
|
|
|
|
|
from PyQt5.QtWidgets import (QTableView, QPushButton, QVBoxLayout, QWidget, QHBoxLayout,
|
|
|
|
|
QLabel, QCheckBox, QSpinBox, QMenu, QFileDialog, QDialog,
|
|
|
|
|
QLineEdit, QFormLayout, QDialogButtonBox, QMessageBox,
|
|
|
|
|
QHeaderView, QToolBar, QAction, QDoubleSpinBox)
|
|
|
|
|
from docx import Document
|
|
|
|
|
from docx.shared import Pt, RGBColor
|
|
|
|
|
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
|
|
|
|
from docx.oxml import parse_xml
|
|
|
|
|
from docx.oxml.ns import nsdecls
|
|
|
|
|
import qtawesome as qta
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
import random
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
from model.ProcedureModel.ProcedureProcessor import 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__()
|
|
|
|
|
self.procedureData = procedureData
|
|
|
|
|
self.procedureId = procedureId
|
|
|
|
|
self.dbManager = dbManager
|
|
|
|
|
self.isRunning = False
|
|
|
|
|
self.isActive = False
|
|
|
|
|
|
|
|
|
|
testSteps = procedureData["测试步骤"]
|
|
|
|
|
|
|
|
|
|
self.protocolManager = Globals.getValue("protocolManage")
|
|
|
|
|
|
|
|
|
|
self.initUi(testSteps)
|
|
|
|
|
self.initExecutionState()
|
|
|
|
|
self.initTimers()
|
|
|
|
|
|
|
|
|
|
def initExecutionState(self):
|
|
|
|
|
"""初始化执行状态"""
|
|
|
|
|
self.currentIndex = 0
|
|
|
|
|
self.remainingCycles = 1
|
|
|
|
|
self.infiniteCycles = False
|
|
|
|
|
self.isFirstRun = True
|
|
|
|
|
self.stepResults = []
|
|
|
|
|
self.remainingTime = 0
|
|
|
|
|
self.totalSteps = 0
|
|
|
|
|
|
|
|
|
|
def initTimers(self):
|
|
|
|
|
"""初始化定时器"""
|
|
|
|
|
self.timer = QTimer()
|
|
|
|
|
self.timer.timeout.connect(self.autoExecuteStep)
|
|
|
|
|
|
|
|
|
|
self.countdownTimer = QTimer()
|
|
|
|
|
self.countdownTimer.timeout.connect(self.updateCountdown)
|
|
|
|
|
|
|
|
|
|
def initUi(self, testSteps):
|
|
|
|
|
"""初始化用户界面"""
|
|
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
|
|
|
|
self.createInfoSection(layout)
|
|
|
|
|
self.createTableSection(layout, testSteps)
|
|
|
|
|
self.createControlSection(layout)
|
|
|
|
|
self.createSettingsSection(layout)
|
|
|
|
|
|
|
|
|
|
self.setLayout(layout)
|
|
|
|
|
self.createToolbar()
|
|
|
|
|
|
|
|
|
|
def createInfoSection(self, layout):
|
|
|
|
|
"""创建信息显示区域"""
|
|
|
|
|
infoLayout = QFormLayout()
|
|
|
|
|
infoLayout.setLabelAlignment(Qt.AlignRight)
|
|
|
|
|
|
|
|
|
|
infoItems = [
|
|
|
|
|
("规程名称:", self.procedureData["规程信息"]["规程名称"]),
|
|
|
|
|
("规程编号:", self.procedureData["规程信息"]["规程编号"]),
|
|
|
|
|
("规程类型:", self.procedureData["规程信息"]["规程类型"]),
|
|
|
|
|
("测试用例:", self.procedureData["测试用例信息"]["测试用例"]),
|
|
|
|
|
("用例编号:", self.procedureData["测试用例信息"]["用例编号"]),
|
|
|
|
|
("工况描述:", self.procedureData["测试用例信息"]["工况描述"])
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for label, value in infoItems:
|
|
|
|
|
infoLayout.addRow(label, QLabel(value))
|
|
|
|
|
|
|
|
|
|
layout.addLayout(infoLayout)
|
|
|
|
|
layout.addSpacing(20)
|
|
|
|
|
|
|
|
|
|
def createTableSection(self, layout, testSteps):
|
|
|
|
|
"""创建表格区域"""
|
|
|
|
|
self.tableModel = StepTableModel(testSteps)
|
|
|
|
|
self.tableView = QTableView()
|
|
|
|
|
self.tableView.setModel(self.tableModel)
|
|
|
|
|
self.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
|
|
|
self.tableView.customContextMenuRequested.connect(self.showContextMenu)
|
|
|
|
|
|
|
|
|
|
self.setupTableHeaders()
|
|
|
|
|
|
|
|
|
|
layout.addWidget(QLabel("测试步骤:"))
|
|
|
|
|
layout.addWidget(self.tableView)
|
|
|
|
|
|
|
|
|
|
def setupTableHeaders(self):
|
|
|
|
|
"""设置表格表头"""
|
|
|
|
|
header = self.tableView.horizontalHeader()
|
|
|
|
|
if header:
|
|
|
|
|
header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
|
|
|
|
|
header.setSectionResizeMode(1, QHeaderView.Stretch)
|
|
|
|
|
header.setSectionResizeMode(2, QHeaderView.ResizeToContents)
|
|
|
|
|
header.setSectionResizeMode(3, QHeaderView.ResizeToContents)
|
|
|
|
|
header.setSectionResizeMode(5, QHeaderView.Stretch)
|
|
|
|
|
|
|
|
|
|
verticalHeader = self.tableView.verticalHeader()
|
|
|
|
|
if verticalHeader:
|
|
|
|
|
self.tableView.setWordWrap(True)
|
|
|
|
|
verticalHeader.setSectionResizeMode(QHeaderView.ResizeToContents)
|
|
|
|
|
|
|
|
|
|
def createControlSection(self, layout):
|
|
|
|
|
"""创建控制按钮区域"""
|
|
|
|
|
controlLayout = QHBoxLayout()
|
|
|
|
|
|
|
|
|
|
self.autoButton = QPushButton(" 开始自动执行")
|
|
|
|
|
self.autoButton.clicked.connect(self.startAutoExecute)
|
|
|
|
|
self.autoButton.setIcon(qta.icon('fa5s.play', color='green'))
|
|
|
|
|
|
|
|
|
|
self.stopButton = QPushButton(" 停止自动执行")
|
|
|
|
|
self.stopButton.clicked.connect(self.stopAutoExecute)
|
|
|
|
|
self.stopButton.setEnabled(False)
|
|
|
|
|
self.stopButton.setIcon(qta.icon('fa5s.stop', color='red'))
|
|
|
|
|
|
|
|
|
|
self.nextButton = QPushButton(" 执行下一步")
|
|
|
|
|
self.nextButton.clicked.connect(self.executeNextStep)
|
|
|
|
|
self.nextButton.setIcon(qta.icon('fa5s.step-forward', color='blue'))
|
|
|
|
|
|
|
|
|
|
self.resetButton = QPushButton(" 完全重置")
|
|
|
|
|
self.resetButton.clicked.connect(self.resetExecution)
|
|
|
|
|
self.resetButton.setIcon(qta.icon('fa5s.redo', color='orange'))
|
|
|
|
|
|
|
|
|
|
self.exportButton = QPushButton(" 生成报告")
|
|
|
|
|
self.exportButton.clicked.connect(self.onExportReportClicked)
|
|
|
|
|
self.exportButton.setIcon(qta.icon('fa5s.file-alt', color='purple'))
|
|
|
|
|
|
|
|
|
|
buttons = [self.autoButton, self.stopButton, self.nextButton,
|
|
|
|
|
self.resetButton, self.exportButton]
|
|
|
|
|
for button in buttons:
|
|
|
|
|
controlLayout.addWidget(button)
|
|
|
|
|
|
|
|
|
|
layout.addLayout(controlLayout)
|
|
|
|
|
|
|
|
|
|
def createSettingsSection(self, layout):
|
|
|
|
|
"""创建设置区域"""
|
|
|
|
|
cycleLayout = QHBoxLayout()
|
|
|
|
|
|
|
|
|
|
# 执行轮次设置
|
|
|
|
|
cycleLayout.addWidget(QLabel("执行轮次:"))
|
|
|
|
|
self.cycleSpin = QSpinBox()
|
|
|
|
|
self.cycleSpin.setRange(1, 999)
|
|
|
|
|
self.cycleSpin.setValue(1)
|
|
|
|
|
cycleLayout.addWidget(self.cycleSpin)
|
|
|
|
|
|
|
|
|
|
self.infiniteCheckbox = QCheckBox("无限循环")
|
|
|
|
|
cycleLayout.addWidget(self.infiniteCheckbox)
|
|
|
|
|
|
|
|
|
|
# 状态显示
|
|
|
|
|
self.statusLabel = QLabel("就绪")
|
|
|
|
|
self.statusLabel.setStyleSheet("color: blue; font-weight: bold;")
|
|
|
|
|
cycleLayout.addWidget(self.statusLabel)
|
|
|
|
|
|
|
|
|
|
# 倒计时显示
|
|
|
|
|
self.countdownLabel = QLabel("")
|
|
|
|
|
self.countdownLabel.setStyleSheet("color: red; font-weight: bold; font-size: 14px;")
|
|
|
|
|
cycleLayout.addWidget(self.countdownLabel)
|
|
|
|
|
|
|
|
|
|
# 步骤间隔设置
|
|
|
|
|
cycleLayout.addWidget(QLabel("步骤间隔(秒):"))
|
|
|
|
|
self.stepIntervalSpin = QDoubleSpinBox()
|
|
|
|
|
self.stepIntervalSpin.setRange(0, 60.0)
|
|
|
|
|
self.stepIntervalSpin.setValue(1)
|
|
|
|
|
self.stepIntervalSpin.setDecimals(2)
|
|
|
|
|
self.stepIntervalSpin.setSingleStep(0.1)
|
|
|
|
|
self.stepIntervalSpin.setToolTip("设置步骤执行计时器的间隔时间(秒)")
|
|
|
|
|
cycleLayout.addWidget(self.stepIntervalSpin)
|
|
|
|
|
|
|
|
|
|
cycleLayout.addStretch()
|
|
|
|
|
layout.addLayout(cycleLayout)
|
|
|
|
|
|
|
|
|
|
def createToolbar(self):
|
|
|
|
|
"""创建工具栏"""
|
|
|
|
|
self.toolbar = QToolBar("执行工具栏")
|
|
|
|
|
self.toolbar.setIconSize(QSize(24, 24))
|
|
|
|
|
|
|
|
|
|
toolbarActions = [
|
|
|
|
|
(qta.icon('fa5s.play', color='green'), "开始执行", self.startAutoExecute),
|
|
|
|
|
(qta.icon('fa5s.stop', color='red'), "停止执行", self.stopAutoExecute),
|
|
|
|
|
(qta.icon('fa5s.step-forward', color='blue'), "下一步", self.executeNextStep),
|
|
|
|
|
(qta.icon('fa5s.redo', color='orange'), "重置", self.resetExecution),
|
|
|
|
|
(qta.icon('fa5s.file-alt', color='purple'), "生成报告", self.onExportReportClicked)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for icon, text, slot in toolbarActions:
|
|
|
|
|
action = QAction(icon, text, self)
|
|
|
|
|
action.triggered.connect(slot)
|
|
|
|
|
self.toolbar.addAction(action)
|
|
|
|
|
if text in ["下一步", "重置"]:
|
|
|
|
|
self.toolbar.addSeparator()
|
|
|
|
|
|
|
|
|
|
def showContextMenu(self, pos):
|
|
|
|
|
"""显示右键菜单"""
|
|
|
|
|
index = self.tableView.indexAt(pos)
|
|
|
|
|
if not index.isValid():
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
menu = QMenu()
|
|
|
|
|
|
|
|
|
|
jumpAction = menu.addAction("从该步骤开始执行")
|
|
|
|
|
jumpAction.triggered.connect(lambda: self.jumpExecute(index.row()))
|
|
|
|
|
|
|
|
|
|
detailAction = menu.addAction("查看步骤详情")
|
|
|
|
|
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():
|
|
|
|
|
stepInfo = self.tableModel.getStepInfo(rowIndex)
|
|
|
|
|
if stepInfo and not stepInfo['isMain']:
|
|
|
|
|
self.executeStep(rowIndex)
|
|
|
|
|
self.currentIndex = rowIndex + 1
|
|
|
|
|
|
|
|
|
|
def showStepDetail(self, row):
|
|
|
|
|
"""显示步骤详情"""
|
|
|
|
|
stepInfo = self.tableModel.getStepInfo(row)
|
|
|
|
|
if not stepInfo:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
detailDialog = QDialog(self)
|
|
|
|
|
detailDialog.setWindowTitle("步骤详情")
|
|
|
|
|
detailDialog.setMinimumWidth(500)
|
|
|
|
|
|
|
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
formLayout = QFormLayout()
|
|
|
|
|
|
|
|
|
|
formLayout.addRow("步骤ID", QLabel(stepInfo['stepId']))
|
|
|
|
|
formLayout.addRow("步骤类型", QLabel("主步骤" if stepInfo['isMain'] else "子步骤"))
|
|
|
|
|
formLayout.addRow("步骤描述", QLabel(stepInfo['description']))
|
|
|
|
|
|
|
|
|
|
if stepInfo['time']:
|
|
|
|
|
formLayout.addRow("执行时间", QLabel(stepInfo['time'].strftime("%Y-%m-%d %H:%M:%S")))
|
|
|
|
|
|
|
|
|
|
if stepInfo['result'] is not None:
|
|
|
|
|
status = "成功" if stepInfo['result'] else "失败"
|
|
|
|
|
formLayout.addRow("执行结果", QLabel(status))
|
|
|
|
|
|
|
|
|
|
layout.addLayout(formLayout)
|
|
|
|
|
|
|
|
|
|
buttonBox = QDialogButtonBox(QDialogButtonBox.Ok)
|
|
|
|
|
buttonBox.accepted.connect(detailDialog.accept)
|
|
|
|
|
layout.addWidget(buttonBox)
|
|
|
|
|
|
|
|
|
|
detailDialog.setLayout(layout)
|
|
|
|
|
detailDialog.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
|
|
|
|
|
self.tabLockRequired.emit(True)
|
|
|
|
|
|
|
|
|
|
self.updateButtonStates(False, True, False, False, False)
|
|
|
|
|
|
|
|
|
|
# 每次开始新的执行时,都需要初始化状态
|
|
|
|
|
if self.isFirstRun:
|
|
|
|
|
self.initializeFirstRun()
|
|
|
|
|
|
|
|
|
|
# 确保每次开始执行时都重新获取设置值
|
|
|
|
|
self.remainingCycles = self.cycleSpin.value()
|
|
|
|
|
self.infiniteCycles = self.infiniteCheckbox.isChecked()
|
|
|
|
|
|
|
|
|
|
# 重置当前索引,确保从第一步开始执行
|
|
|
|
|
self.currentIndex = 0
|
|
|
|
|
|
|
|
|
|
stepInterval = int(self.stepIntervalSpin.value() * 1000)
|
|
|
|
|
self.timer.start(stepInterval)
|
|
|
|
|
|
|
|
|
|
cycleNum = self.getCurrentCycleNumber()
|
|
|
|
|
self.updateStatusDisplay(f"开始执行 - 第{cycleNum}轮", "green")
|
|
|
|
|
self.startCountdown()
|
|
|
|
|
|
|
|
|
|
def updateButtonStates(self, auto, stop, next, reset, export):
|
|
|
|
|
"""更新按钮状态"""
|
|
|
|
|
self.autoButton.setEnabled(auto)
|
|
|
|
|
self.stopButton.setEnabled(stop)
|
|
|
|
|
self.nextButton.setEnabled(next)
|
|
|
|
|
self.resetButton.setEnabled(reset)
|
|
|
|
|
self.exportButton.setEnabled(export)
|
|
|
|
|
|
|
|
|
|
def initializeFirstRun(self):
|
|
|
|
|
"""初始化首次执行"""
|
|
|
|
|
self.stepResults = []
|
|
|
|
|
self.tableModel.resetExecutionState()
|
|
|
|
|
self.isFirstRun = False
|
|
|
|
|
self.createNewExecutionRecord()
|
|
|
|
|
|
|
|
|
|
def stopAutoExecute(self):
|
|
|
|
|
"""停止自动执行"""
|
|
|
|
|
self.isRunning = False
|
|
|
|
|
self.timer.stop()
|
|
|
|
|
self.updateButtonStates(True, False, True, True, True)
|
|
|
|
|
|
|
|
|
|
self.saveCurrentResults()
|
|
|
|
|
self.updateStatusDisplay("执行已停止", "orange")
|
|
|
|
|
self.stopCountdown()
|
|
|
|
|
|
|
|
|
|
self.executionFinished.emit(self)
|
|
|
|
|
self.tabLockRequired.emit(False)
|
|
|
|
|
|
|
|
|
|
def saveCurrentResults(self):
|
|
|
|
|
"""保存当前结果"""
|
|
|
|
|
if hasattr(self, 'currentExecutionId') and self.stepResults:
|
|
|
|
|
try:
|
|
|
|
|
json.dumps(self.stepResults)
|
|
|
|
|
self.dbManager.updateStepResults(self.currentExecutionId, self.stepResults)
|
|
|
|
|
print("执行停止,当前轮次结果已保存")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print("stepResults不能序列化", self.stepResults)
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
def autoExecuteStep(self):
|
|
|
|
|
"""自动执行步骤"""
|
|
|
|
|
if self.currentIndex < self.tableModel.rowCount():
|
|
|
|
|
self.executeCurrentStep()
|
|
|
|
|
else:
|
|
|
|
|
self.handleCycleCompletion()
|
|
|
|
|
|
|
|
|
|
def executeCurrentStep(self):
|
|
|
|
|
"""执行当前步骤"""
|
|
|
|
|
stepInfo = self.tableModel.getStepInfo(self.currentIndex)
|
|
|
|
|
if stepInfo and not stepInfo['isMain']:
|
|
|
|
|
self.executeStep(self.currentIndex)
|
|
|
|
|
self.currentIndex += 1
|
|
|
|
|
self.startCountdown()
|
|
|
|
|
|
|
|
|
|
def handleCycleCompletion(self):
|
|
|
|
|
"""处理轮次完成"""
|
|
|
|
|
self.saveCurrentResults()
|
|
|
|
|
|
|
|
|
|
if self.remainingCycles > 1:
|
|
|
|
|
self.prepareNextCycle()
|
|
|
|
|
else:
|
|
|
|
|
self.finishExecution()
|
|
|
|
|
|
|
|
|
|
def prepareNextCycle(self):
|
|
|
|
|
"""准备下一轮执行"""
|
|
|
|
|
if not self.infiniteCycles:
|
|
|
|
|
self.remainingCycles -= 1
|
|
|
|
|
|
|
|
|
|
self.timer.stop()
|
|
|
|
|
self.currentIndex = 0
|
|
|
|
|
|
|
|
|
|
# if self.infiniteCycles != 0:
|
|
|
|
|
|
|
|
|
|
self.createNewExecutionRecord()
|
|
|
|
|
|
|
|
|
|
# 只在非最后一轮时清除表格状态,最后一轮完成后保留状态信息
|
|
|
|
|
# if self.remainingCycles > 0 or self.infiniteCycles:
|
|
|
|
|
self.tableModel.resetAll()
|
|
|
|
|
self.stepResults = []
|
|
|
|
|
|
|
|
|
|
cycleNum = self.getCurrentCycleNumber()
|
|
|
|
|
self.updateStatusDisplay(f"执行中 - 第{cycleNum}轮", "green")
|
|
|
|
|
|
|
|
|
|
self.startCountdown()
|
|
|
|
|
|
|
|
|
|
stepInterval = int(self.stepIntervalSpin.value() * 1000)
|
|
|
|
|
self.timer.start(stepInterval)
|
|
|
|
|
|
|
|
|
|
def finishExecution(self):
|
|
|
|
|
"""完成执行"""
|
|
|
|
|
self.stopAutoExecute()
|
|
|
|
|
|
|
|
|
|
# 计算最终执行统计
|
|
|
|
|
totalSteps = len([step for step in self.tableModel.stepData])
|
|
|
|
|
executedSteps = len([step for step in self.tableModel.stepData if step.get('executed', False)])
|
|
|
|
|
successSteps = len([step for step in self.tableModel.stepData if step.get('executed', False) and step.get('result', False)])
|
|
|
|
|
failedSteps = executedSteps - successSteps
|
|
|
|
|
successRate = (successSteps/executedSteps*100) if executedSteps > 0 else 0
|
|
|
|
|
|
|
|
|
|
# 显示最终执行状态
|
|
|
|
|
statusMessage = f"执行完成 - 总步骤: {totalSteps}, 成功: {successSteps}, 失败: {failedSteps}, 成功率: {successRate:.1f}%"
|
|
|
|
|
self.updateStatusDisplay(statusMessage, "green")
|
|
|
|
|
|
|
|
|
|
# 重置执行状态,为下次执行做准备
|
|
|
|
|
self.isFirstRun = True
|
|
|
|
|
self.currentIndex = 0
|
|
|
|
|
|
|
|
|
|
self.executionFinished.emit(self)
|
|
|
|
|
|
|
|
|
|
def createNewExecutionRecord(self):
|
|
|
|
|
"""创建新的执行记录"""
|
|
|
|
|
executionTime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
self.currentExecutionId = self.dbManager.insertProcedureExecution(
|
|
|
|
|
self.procedureId, executionTime
|
|
|
|
|
)
|
|
|
|
|
print(f"创建新的执行记录,ID: {self.currentExecutionId}")
|
|
|
|
|
|
|
|
|
|
def getCurrentCycleNumber(self):
|
|
|
|
|
"""获取当前轮次数"""
|
|
|
|
|
if self.infiniteCycles:
|
|
|
|
|
return "无限"
|
|
|
|
|
else:
|
|
|
|
|
totalCycles = self.cycleSpin.value()
|
|
|
|
|
# 当前正在执行的轮次 = 总轮次 - 剩余轮次 + 1
|
|
|
|
|
currentCycle = self.remainingCycles
|
|
|
|
|
return f"{currentCycle}/{totalCycles}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def executeNextStep(self):
|
|
|
|
|
"""执行下一步"""
|
|
|
|
|
self.isActive = True
|
|
|
|
|
self.tabLockRequired.emit(True)
|
|
|
|
|
|
|
|
|
|
if self.currentIndex < self.tableModel.rowCount():
|
|
|
|
|
stepInfo = self.tableModel.getStepInfo(self.currentIndex)
|
|
|
|
|
if stepInfo and not stepInfo['isMain']:
|
|
|
|
|
self.executeStep(self.currentIndex)
|
|
|
|
|
self.currentIndex += 1
|
|
|
|
|
|
|
|
|
|
def executeStep(self, row):
|
|
|
|
|
"""执行步骤"""
|
|
|
|
|
stepInfo = self.tableModel.getStepInfo(row)
|
|
|
|
|
if not stepInfo:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
print(f"开始执行步骤 {stepInfo['stepId']}: {stepInfo['description']}")
|
|
|
|
|
result = self.handleStep(row, stepInfo)
|
|
|
|
|
|
|
|
|
|
if result is None:
|
|
|
|
|
print(f"警告:步骤 {stepInfo['stepId']} 返回了None结果,设置为False")
|
|
|
|
|
result = '未检测到关键字'
|
|
|
|
|
|
|
|
|
|
executionTime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
stepResult = {
|
|
|
|
|
'step_id': str(stepInfo.get('stepId', '')),
|
|
|
|
|
'step_description': str(stepInfo.get('description', '')),
|
|
|
|
|
'execution_time': executionTime,
|
|
|
|
|
'result': result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.stepResults.append(stepResult)
|
|
|
|
|
|
|
|
|
|
if hasattr(self, 'currentExecutionId'):
|
|
|
|
|
self.dbManager.updateStepResults(self.currentExecutionId, self.stepResults)
|
|
|
|
|
|
|
|
|
|
success = self.tableModel.updateStepResult(row, result, datetime.now())
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
def handleStep(self, rowIndex, stepInfo):
|
|
|
|
|
"""处理步骤"""
|
|
|
|
|
description = stepInfo['description']
|
|
|
|
|
stepId = stepInfo['stepId']
|
|
|
|
|
|
|
|
|
|
print(f"处理步骤 {stepId}: {description}")
|
|
|
|
|
|
|
|
|
|
# 从关键词字段库中提取关键词信息
|
|
|
|
|
keywordInfo = self.extractKeywordFromText(description)
|
|
|
|
|
|
|
|
|
|
if keywordInfo:
|
|
|
|
|
operationType = keywordInfo['operationType']
|
|
|
|
|
keyword = keywordInfo['keyword']
|
|
|
|
|
print(f"检测到关键词: {keyword}, 操作类型: {operationType}")
|
|
|
|
|
|
|
|
|
|
# 根据操作类型执行相应的处理
|
|
|
|
|
stepHandlers = {
|
|
|
|
|
"set": self.performSetOperation,
|
|
|
|
|
"check": self.performCheckOperation,
|
|
|
|
|
"wait": self.performWaitOperation,
|
|
|
|
|
"deltaT": self.performDeltaTOperation
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handler = stepHandlers.get(operationType)
|
|
|
|
|
if handler:
|
|
|
|
|
return handler(stepId, description, keyword)
|
|
|
|
|
|
|
|
|
|
# 如果没有找到匹配的关键词,使用默认的模拟执行
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def extractKeywordFromText(self, text):
|
|
|
|
|
"""从文本中提取关键词信息"""
|
|
|
|
|
if not hasattr(self, 'dbManager') or not self.dbManager:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
keywords = self.dbManager.getKeywordByText(text)
|
|
|
|
|
if keywords:
|
|
|
|
|
# 返回第一个匹配的关键词(按长度降序排列,最长的优先)
|
|
|
|
|
keyword, operationType = keywords[0]
|
|
|
|
|
return {
|
|
|
|
|
'keyword': keyword,
|
|
|
|
|
'operationType': operationType
|
|
|
|
|
}
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"提取关键词时出错: {str(e)}")
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def performSetOperation(self, stepId, description, keyword):
|
|
|
|
|
"""执行设置操作"""
|
|
|
|
|
# print(f"执行设置操作 (步骤 {stepId}): {description}")
|
|
|
|
|
|
|
|
|
|
# 简单匹配等号前变量名和等号后数值
|
|
|
|
|
variablePattern = r'([^\s=]+)=([0-9]+(?:\.[0-9]+)?)'
|
|
|
|
|
matches = re.findall(variablePattern, description)
|
|
|
|
|
|
|
|
|
|
if matches:
|
|
|
|
|
print(f"检测到 {len(matches)} 个需要设置的变量:")
|
|
|
|
|
result = ''
|
|
|
|
|
for varName, varValue in matches:
|
|
|
|
|
varName = varName.strip()
|
|
|
|
|
print(f" 变量: {varName} = {varValue}")
|
|
|
|
|
success = self.protocolManager.writeVariableValue(varName, float(varValue))
|
|
|
|
|
if success:
|
|
|
|
|
result += f"{varName} = {varValue}\n"
|
|
|
|
|
else:
|
|
|
|
|
result += f"{varName}强制失败\n"
|
|
|
|
|
return result
|
|
|
|
|
else:
|
|
|
|
|
return '"强制失败,未检测到关键字"'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def performCheckOperation(self, stepId, description, keyword):
|
|
|
|
|
"""执行检查操作"""
|
|
|
|
|
print(f"执行检查操作 (步骤 {stepId}): {description}")
|
|
|
|
|
|
|
|
|
|
# 支持多种比较符号的正则表达式
|
|
|
|
|
# 匹配格式:变量名 比较符号 数值
|
|
|
|
|
# 支持的比较符号:=, ==, >, <, >=, <=, !=, <>
|
|
|
|
|
variablePattern = r'([^\s=><!]+)\s*(=|==|>|<|>=|<=|!=|<>)\s*([0-9]+(?:\.[0-9]+)?)'
|
|
|
|
|
matches = re.findall(variablePattern, description)
|
|
|
|
|
|
|
|
|
|
if matches:
|
|
|
|
|
# print(f"检测到 {len(matches)} 个需要检查的变量:")
|
|
|
|
|
result = ''
|
|
|
|
|
allPassed = True
|
|
|
|
|
|
|
|
|
|
for varName, operator, expectedValue in matches:
|
|
|
|
|
varName = varName.strip()
|
|
|
|
|
expectedValue = float(expectedValue)
|
|
|
|
|
print(f" 检查变量: {varName} {operator} {expectedValue}")
|
|
|
|
|
|
|
|
|
|
# 读取变量实际值
|
|
|
|
|
actualValue = self.protocolManager.readVariableValue(varName)
|
|
|
|
|
|
|
|
|
|
if actualValue is not None:
|
|
|
|
|
try:
|
|
|
|
|
actualValue = float(actualValue)
|
|
|
|
|
# 根据操作符进行比较
|
|
|
|
|
comparisonResult = self.compareValues(actualValue, operator, expectedValue)
|
|
|
|
|
|
|
|
|
|
if comparisonResult:
|
|
|
|
|
result += f"{varName} = {actualValue} {operator} {expectedValue} ✓\n"
|
|
|
|
|
print(f" ✓ 检查通过: {varName} = {actualValue} {operator} {expectedValue}")
|
|
|
|
|
else:
|
|
|
|
|
result += f"{varName} = {actualValue} {operator} {expectedValue} ✗\n"
|
|
|
|
|
print(f" ✗ 检查失败: {varName} = {actualValue} {operator} {expectedValue}")
|
|
|
|
|
allPassed = False
|
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
|
result += f"{varName} = {actualValue} ✗ (类型错误)\n"
|
|
|
|
|
print(f" ✗ 类型错误: {varName} = {actualValue}")
|
|
|
|
|
allPassed = False
|
|
|
|
|
else:
|
|
|
|
|
result += f"{varName} 读取失败 ✗\n"
|
|
|
|
|
print(f" ✗ 读取失败: {varName}")
|
|
|
|
|
allPassed = False
|
|
|
|
|
|
|
|
|
|
if allPassed:
|
|
|
|
|
return result
|
|
|
|
|
else:
|
|
|
|
|
return result
|
|
|
|
|
else:
|
|
|
|
|
return "强制失败,未检测到关键字"
|
|
|
|
|
|
|
|
|
|
def compareValues(self, actualValue, operator, expectedValue):
|
|
|
|
|
"""
|
|
|
|
|
比较实际值和期望值
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
actualValue: 实际值
|
|
|
|
|
operator: 比较操作符 (=, ==, >, <, >=, <=, !=, <>)
|
|
|
|
|
expectedValue: 期望值
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
bool: 比较结果
|
|
|
|
|
"""
|
|
|
|
|
# 对于等于操作,允许一定的误差容限
|
|
|
|
|
tolerance = 0.001 # 0.1%的误差容限
|
|
|
|
|
|
|
|
|
|
if operator in ['=', '==']:
|
|
|
|
|
# 等于比较,允许误差
|
|
|
|
|
return abs(actualValue - expectedValue) <= (expectedValue * tolerance)
|
|
|
|
|
elif operator == '>':
|
|
|
|
|
# 大于比较
|
|
|
|
|
return actualValue > expectedValue
|
|
|
|
|
elif operator == '<':
|
|
|
|
|
# 小于比较
|
|
|
|
|
return actualValue < expectedValue
|
|
|
|
|
elif operator == '>=':
|
|
|
|
|
# 大于等于比较
|
|
|
|
|
return actualValue >= expectedValue
|
|
|
|
|
elif operator == '<=':
|
|
|
|
|
# 小于等于比较
|
|
|
|
|
return actualValue <= expectedValue
|
|
|
|
|
elif operator in ['!=', '<>']:
|
|
|
|
|
# 不等于比较,允许误差
|
|
|
|
|
return abs(actualValue - expectedValue) > (expectedValue * tolerance)
|
|
|
|
|
else:
|
|
|
|
|
# 未知操作符,默认返回False
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def performWaitOperation(self, stepId, description, keyword):
|
|
|
|
|
"""执行等待操作"""
|
|
|
|
|
print(f"执行等待操作 (步骤 {stepId}): {description}")
|
|
|
|
|
# 这里可以添加具体的等待操作逻辑
|
|
|
|
|
return '执行成功'
|
|
|
|
|
|
|
|
|
|
def performDeltaTOperation(self, stepId, description, keyword):
|
|
|
|
|
"""执行接收操作"""
|
|
|
|
|
print(f"执行接收操作 (步骤 {stepId}): {description}")
|
|
|
|
|
# 这里可以添加具体的接收操作逻辑
|
|
|
|
|
return random.random() < 0.88
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def resetExecution(self, fromAuto=False):
|
|
|
|
|
"""重置执行"""
|
|
|
|
|
# print(111111111111)
|
|
|
|
|
|
|
|
|
|
self.tableModel.resetAll()
|
|
|
|
|
self.currentIndex = 0
|
|
|
|
|
|
|
|
|
|
if not fromAuto:
|
|
|
|
|
self.stopAutoExecute()
|
|
|
|
|
|
|
|
|
|
self.cycleSpin.setValue(1)
|
|
|
|
|
self.infiniteCheckbox.setChecked(False)
|
|
|
|
|
self.isRunning = False
|
|
|
|
|
self.isActive = False
|
|
|
|
|
self.isFirstRun = True
|
|
|
|
|
self.stepResults = []
|
|
|
|
|
|
|
|
|
|
if hasattr(self, 'currentExecutionId'):
|
|
|
|
|
del self.currentExecutionId
|
|
|
|
|
|
|
|
|
|
self.tabLockRequired.emit(False)
|
|
|
|
|
self.updateStatusDisplay("已重置", "blue")
|
|
|
|
|
self.resetCountdown()
|
|
|
|
|
|
|
|
|
|
def onExportReportClicked(self):
|
|
|
|
|
"""导出报告点击事件"""
|
|
|
|
|
self.generateReport()
|
|
|
|
|
|
|
|
|
|
def generateReport(self):
|
|
|
|
|
"""生成执行报告"""
|
|
|
|
|
try:
|
|
|
|
|
defaultName = f"{self.procedureData['规程信息']['规程名称']}_{self.procedureData['规程信息']['规程编号']}_执行报告.docx"
|
|
|
|
|
filePath, _ = QFileDialog.getSaveFileName(
|
|
|
|
|
self, "保存报告", defaultName, "Word文档 (*.docx)"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not filePath:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
doc = Document()
|
|
|
|
|
self.setupDocumentStyles(doc)
|
|
|
|
|
|
|
|
|
|
title = doc.add_heading(f"{self.procedureData['规程信息']['规程名称']} 执行报告", 0)
|
|
|
|
|
title.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
|
|
|
|
|
|
|
|
|
timePara = doc.add_paragraph()
|
|
|
|
|
timePara.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
|
|
|
|
timeRun = timePara.add_run(f"报告生成时间: {datetime.now().strftime('%Y年%m月%d日 %H:%M:%S')}")
|
|
|
|
|
timeRun.font.size = Pt(10)
|
|
|
|
|
timeRun.font.color.rgb = RGBColor(128, 128, 128)
|
|
|
|
|
|
|
|
|
|
doc.add_paragraph()
|
|
|
|
|
|
|
|
|
|
doc.add_heading("1. 规程基本信息", level=1)
|
|
|
|
|
self.addProcedureInfoTable(doc)
|
|
|
|
|
|
|
|
|
|
doc.add_paragraph()
|
|
|
|
|
|
|
|
|
|
doc.add_heading("2. 执行结果统计", level=1)
|
|
|
|
|
self.addExecutionStatsTable(doc)
|
|
|
|
|
|
|
|
|
|
doc.add_paragraph()
|
|
|
|
|
|
|
|
|
|
doc.add_heading("3. 详细步骤执行结果", level=1)
|
|
|
|
|
self.addDetailedStepsTable(doc)
|
|
|
|
|
|
|
|
|
|
doc.add_paragraph()
|
|
|
|
|
doc.add_heading("4. 执行总结", level=1)
|
|
|
|
|
self.addExecutionSummary(doc)
|
|
|
|
|
|
|
|
|
|
doc.save(filePath)
|
|
|
|
|
|
|
|
|
|
QMessageBox.information(self, "导出成功", f"报告已保存到:\n{filePath}")
|
|
|
|
|
return filePath
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
QMessageBox.critical(self, "导出错误", f"生成报告时出错:\n{str(e)}")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def setupDocumentStyles(self, doc):
|
|
|
|
|
"""设置文档样式"""
|
|
|
|
|
style = doc.styles['Normal']
|
|
|
|
|
style.font.name = '微软雅黑'
|
|
|
|
|
style.font.size = Pt(10.5)
|
|
|
|
|
|
|
|
|
|
for i in range(1, 4):
|
|
|
|
|
headingStyle = doc.styles[f'Heading {i}']
|
|
|
|
|
headingStyle.font.name = '微软雅黑'
|
|
|
|
|
headingStyle.font.bold = True
|
|
|
|
|
if i == 1:
|
|
|
|
|
headingStyle.font.size = Pt(16)
|
|
|
|
|
headingStyle.font.color.rgb = RGBColor(0, 0, 139)
|
|
|
|
|
elif i == 2:
|
|
|
|
|
headingStyle.font.size = Pt(14)
|
|
|
|
|
headingStyle.font.color.rgb = RGBColor(47, 84, 150)
|
|
|
|
|
else:
|
|
|
|
|
headingStyle.font.size = Pt(12)
|
|
|
|
|
headingStyle.font.color.rgb = RGBColor(68, 114, 196)
|
|
|
|
|
|
|
|
|
|
def addProcedureInfoTable(self, doc):
|
|
|
|
|
"""添加规程信息表格"""
|
|
|
|
|
infoTable = doc.add_table(rows=1, cols=2)
|
|
|
|
|
infoTable.style = 'Table Grid'
|
|
|
|
|
infoTable.autofit = True
|
|
|
|
|
|
|
|
|
|
hdrCells = infoTable.rows[0].cells
|
|
|
|
|
hdrCells[0].text = "项目"
|
|
|
|
|
hdrCells[1].text = "内容"
|
|
|
|
|
|
|
|
|
|
for cell in hdrCells:
|
|
|
|
|
cell.paragraphs[0].runs[0].font.bold = True
|
|
|
|
|
cell.paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 255, 255)
|
|
|
|
|
cell._tc.get_or_add_tcPr().append(parse_xml(f'<w:shd {nsdecls("w")} w:fill="4472C4"/>'))
|
|
|
|
|
|
|
|
|
|
infoItems = [
|
|
|
|
|
("规程名称", self.procedureData['规程信息']['规程名称']),
|
|
|
|
|
("规程编号", self.procedureData['规程信息']['规程编号']),
|
|
|
|
|
("规程类型", self.procedureData['规程信息']['规程类型']),
|
|
|
|
|
("测试用例", self.procedureData['测试用例信息']['测试用例']),
|
|
|
|
|
("用例编号", self.procedureData['测试用例信息']['用例编号']),
|
|
|
|
|
("工况描述", self.procedureData['测试用例信息']['工况描述'])
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for itemName, itemValue in infoItems:
|
|
|
|
|
rowCells = infoTable.add_row().cells
|
|
|
|
|
rowCells[0].text = itemName
|
|
|
|
|
rowCells[1].text = str(itemValue)
|
|
|
|
|
rowCells[0].paragraphs[0].runs[0].font.bold = True
|
|
|
|
|
rowCells[0].paragraphs[0].runs[0].font.color.rgb = RGBColor(68, 114, 196)
|
|
|
|
|
|
|
|
|
|
def addExecutionStatsTable(self, doc):
|
|
|
|
|
"""添加执行统计表格"""
|
|
|
|
|
totalSteps = len([step for step in self.tableModel.stepData])
|
|
|
|
|
executedSteps = len([step for step in self.tableModel.stepData if step.get('executed', False)])
|
|
|
|
|
successSteps = len([step for step in self.tableModel.stepData if step.get('executed', False) and step.get('result', False)])
|
|
|
|
|
failedSteps = executedSteps - successSteps
|
|
|
|
|
successRate = (successSteps/executedSteps*100) if executedSteps > 0 else 0
|
|
|
|
|
|
|
|
|
|
statsTable = doc.add_table(rows=1, cols=2)
|
|
|
|
|
statsTable.style = 'Table Grid'
|
|
|
|
|
statsTable.autofit = True
|
|
|
|
|
|
|
|
|
|
statsHdrCells = statsTable.rows[0].cells
|
|
|
|
|
statsHdrCells[0].text = "统计项目"
|
|
|
|
|
statsHdrCells[1].text = "数量"
|
|
|
|
|
|
|
|
|
|
for cell in statsHdrCells:
|
|
|
|
|
cell.paragraphs[0].runs[0].font.bold = True
|
|
|
|
|
cell.paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 255, 255)
|
|
|
|
|
cell._tc.get_or_add_tcPr().append(parse_xml(f'<w:shd {nsdecls("w")} w:fill="70AD47"/>'))
|
|
|
|
|
|
|
|
|
|
statsItems = [
|
|
|
|
|
("总步骤数", str(totalSteps)),
|
|
|
|
|
("已执行步骤数", str(executedSteps)),
|
|
|
|
|
("成功步骤数", str(successSteps)),
|
|
|
|
|
("失败步骤数", str(failedSteps)),
|
|
|
|
|
("执行成功率", f"{successRate:.1f}%")
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for itemName, itemValue in statsItems:
|
|
|
|
|
rowCells = statsTable.add_row().cells
|
|
|
|
|
rowCells[0].text = itemName
|
|
|
|
|
rowCells[1].text = itemValue
|
|
|
|
|
rowCells[0].paragraphs[0].runs[0].font.bold = True
|
|
|
|
|
rowCells[0].paragraphs[0].runs[0].font.color.rgb = RGBColor(68, 114, 196)
|
|
|
|
|
|
|
|
|
|
if "成功率" in itemName:
|
|
|
|
|
if successRate >= 90:
|
|
|
|
|
rowCells[1].paragraphs[0].runs[0].font.color.rgb = RGBColor(0, 176, 80)
|
|
|
|
|
elif successRate >= 70:
|
|
|
|
|
rowCells[1].paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 192, 0)
|
|
|
|
|
else:
|
|
|
|
|
rowCells[1].paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 0, 0)
|
|
|
|
|
|
|
|
|
|
def addDetailedStepsTable(self, doc):
|
|
|
|
|
"""添加详细步骤表格"""
|
|
|
|
|
stepsTable = doc.add_table(rows=1, cols=5)
|
|
|
|
|
stepsTable.style = 'Table Grid'
|
|
|
|
|
stepsTable.autofit = True
|
|
|
|
|
|
|
|
|
|
stepsHdrCells = stepsTable.rows[0].cells
|
|
|
|
|
stepsHdrCells[0].text = "步骤ID"
|
|
|
|
|
stepsHdrCells[1].text = "步骤描述"
|
|
|
|
|
stepsHdrCells[2].text = "执行时间"
|
|
|
|
|
stepsHdrCells[3].text = "执行结果"
|
|
|
|
|
stepsHdrCells[4].text = "备注"
|
|
|
|
|
|
|
|
|
|
for cell in stepsHdrCells:
|
|
|
|
|
cell.paragraphs[0].runs[0].font.bold = True
|
|
|
|
|
cell.paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 255, 255)
|
|
|
|
|
cell._tc.get_or_add_tcPr().append(parse_xml(f'<w:shd {nsdecls("w")} w:fill="5B9BD5"/>'))
|
|
|
|
|
|
|
|
|
|
for step in self.tableModel.stepData:
|
|
|
|
|
rowCells = stepsTable.add_row().cells
|
|
|
|
|
rowCells[0].text = step.get('stepId', '')
|
|
|
|
|
rowCells[1].text = step.get('description', '')
|
|
|
|
|
|
|
|
|
|
if step.get('executed', False):
|
|
|
|
|
rowCells[2].text = step.get('time', '').strftime("%Y-%m-%d %H:%M:%S") if step.get('time') else ''
|
|
|
|
|
result = step.get('result', False)
|
|
|
|
|
rowCells[3].text = "✓ 成功" if result else "✗ 失败"
|
|
|
|
|
rowCells[4].text = "正常执行" if result else "执行失败"
|
|
|
|
|
|
|
|
|
|
if result:
|
|
|
|
|
rowCells[3].paragraphs[0].runs[0].font.color.rgb = RGBColor(0, 176, 80)
|
|
|
|
|
rowCells[3]._tc.get_or_add_tcPr().append(parse_xml(f'<w:shd {nsdecls("w")} w:fill="C6EFCE"/>'))
|
|
|
|
|
rowCells[4].paragraphs[0].runs[0].font.color.rgb = RGBColor(0, 176, 80)
|
|
|
|
|
else:
|
|
|
|
|
rowCells[3].paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 0, 0)
|
|
|
|
|
rowCells[3]._tc.get_or_add_tcPr().append(parse_xml(f'<w:shd {nsdecls("w")} w:fill="FFC7CE"/>'))
|
|
|
|
|
rowCells[4].paragraphs[0].runs[0].font.color.rgb = RGBColor(255, 0, 0)
|
|
|
|
|
else:
|
|
|
|
|
rowCells[2].text = ""
|
|
|
|
|
rowCells[3].text = "⏸ 未执行"
|
|
|
|
|
rowCells[4].text = "步骤未执行"
|
|
|
|
|
rowCells[3].paragraphs[0].runs[0].font.color.rgb = RGBColor(128, 128, 128)
|
|
|
|
|
rowCells[4].paragraphs[0].runs[0].font.color.rgb = RGBColor(128, 128, 128)
|
|
|
|
|
|
|
|
|
|
def addExecutionSummary(self, doc):
|
|
|
|
|
"""添加执行总结"""
|
|
|
|
|
totalSteps = len([step for step in self.tableModel.stepData])
|
|
|
|
|
executedSteps = len([step for step in self.tableModel.stepData if step.get('executed', False)])
|
|
|
|
|
successSteps = len([step for step in self.tableModel.stepData if step.get('executed', False) and step.get('result', False)])
|
|
|
|
|
failedSteps = executedSteps - successSteps
|
|
|
|
|
successRate = (successSteps/executedSteps*100) if executedSteps > 0 else 0
|
|
|
|
|
|
|
|
|
|
summaryPara = doc.add_paragraph()
|
|
|
|
|
summaryPara.add_run("本次规程执行总结:\n").font.bold = True
|
|
|
|
|
|
|
|
|
|
summaryText = f"""
|
|
|
|
|
• 规程名称:{self.procedureData['规程信息']['规程名称']}
|
|
|
|
|
• 执行时间:{datetime.now().strftime('%Y年%m月%d日 %H:%M:%S')}
|
|
|
|
|
• 总步骤数:{totalSteps} 个
|
|
|
|
|
• 已执行步骤:{executedSteps} 个
|
|
|
|
|
• 成功步骤:{successSteps} 个
|
|
|
|
|
• 失败步骤:{failedSteps} 个
|
|
|
|
|
• 执行成功率:{successRate:.1f}%
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
if successRate >= 90:
|
|
|
|
|
summaryText += "✓ 执行结果:优秀 - 规程执行成功,所有关键步骤均已完成。"
|
|
|
|
|
elif successRate >= 70:
|
|
|
|
|
summaryText += "⚠ 执行结果:良好 - 规程基本执行成功,部分步骤存在问题需要关注。"
|
|
|
|
|
else:
|
|
|
|
|
summaryText += "✗ 执行结果:失败 - 规程执行存在较多问题,需要重新执行或检查。"
|
|
|
|
|
|
|
|
|
|
summaryPara.add_run(summaryText)
|
|
|
|
|
|
|
|
|
|
for run in summaryPara.runs:
|
|
|
|
|
run.font.size = Pt(11)
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
if minutes > 0:
|
|
|
|
|
countdownText = f"当前轮次剩余: {minutes:02d}:{seconds:02d}"
|
|
|
|
|
else:
|
|
|
|
|
countdownText = f"当前轮次剩余: {seconds}秒"
|
|
|
|
|
|
|
|
|
|
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(countdownText)
|
|
|
|
|
self.remainingTime -= 1
|
|
|
|
|
else:
|
|
|
|
|
self.countdownLabel.setText("当前轮次完成")
|
|
|
|
|
self.countdownLabel.setStyleSheet("color: green; font-weight: bold; font-size: 14px;")
|
|
|
|
|
self.countdownTimer.stop()
|
|
|
|
|
|
|
|
|
|
def resetCountdown(self):
|
|
|
|
|
"""重置倒计时"""
|
|
|
|
|
self.stopCountdown()
|
|
|
|
|
self.remainingTime = 0
|
|
|
|
|
self.totalSteps = 0
|
|
|
|
|
|
|
|
|
|
def isExecutionCompleted(self):
|
|
|
|
|
"""检查执行是否完成"""
|
|
|
|
|
return self.currentIndex >= self.tableModel.rowCount()
|
|
|
|
|
|
|
|
|
|
def getExecutionProgress(self):
|
|
|
|
|
"""获取执行进度"""
|
|
|
|
|
totalSteps = self.tableModel.rowCount()
|
|
|
|
|
completedSteps = self.currentIndex
|
|
|
|
|
return completedSteps, totalSteps
|
|
|
|
|
|
|
|
|
|
def showEvent(self, event):
|
|
|
|
|
"""显示事件"""
|
|
|
|
|
super().showEvent(event)
|
|
|
|
|
if not hasattr(self, '_hasResizedRows'):
|
|
|
|
|
self.tableView.resizeRowsToContents()
|
|
|
|
|
self._hasResizedRows = True
|