main
zcwBit 4 months ago
parent 6d52a2d969
commit c1915ae41d

@ -90,6 +90,14 @@ class ProcedureManager(QMainWindow):
self.statusBar = QStatusBar() self.statusBar = QStatusBar()
self.setStatusBar(self.statusBar) 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): def initUI(self):
# 加载样式表 # 加载样式表
self.loadStylesheet() self.loadStylesheet()
@ -148,26 +156,21 @@ class ProcedureManager(QMainWindow):
# 修改:锁定/解锁标签页方法(增加关闭按钮控制) # 修改:锁定/解锁标签页方法(增加关闭按钮控制)
def lockTabs(self, lock, currentIndex): def lockTabs(self, lock, currentIndex):
"""锁定或解锁标签页""" """锁定或解锁标签页只用Qt原生关闭按钮方案"""
self.activeExecutorIndex = currentIndex if lock else -1 self.activeExecutorIndex = currentIndex if lock else -1
self.tabs.setTabsClosable(True)
# 遍历所有标签页
for index in range(self.tabs.count()): for index in range(self.tabs.count()):
# 当前执行器标签页保持可用,其他标签页根据锁定状态设置
enable = not lock or index == currentIndex enable = not lock or index == currentIndex
self.tabs.setTabEnabled(index, enable) self.tabs.setTabEnabled(index, enable)
# 特殊处理:规程管理标签页(索引0)始终可用
if index == 0: if index == 0:
self.tabs.setTabEnabled(0, True) self.tabs.setTabEnabled(0, True)
if index > 0:
# 设置关闭按钮状态:执行期间禁用关闭按钮 if lock and index != currentIndex:
if index > 0: # 非规程管理标签页 self.tabs.tabBar().setTabButton(index, QTabBar.RightSide, None)
self.tabs.tabBar().setTabButton( # 解锁时强制恢复所有关闭按钮
index, if not lock:
QTabBar.RightSide, # 关闭按钮位置 self.tabs.setTabsClosable(False)
None if lock and index != currentIndex else self.tabs.tabBar().tabButton(index, QTabBar.RightSide) self.tabs.setTabsClosable(True)
)
# 修改:打开规程执行界面(显示规程全名) # 修改:打开规程执行界面(显示规程全名)
def openProcedureInExecutor(self, item=None): def openProcedureInExecutor(self, item=None):
@ -206,6 +209,8 @@ class ProcedureManager(QMainWindow):
# 切换到新添加的标签页 # 切换到新添加的标签页
self.tabs.setCurrentWidget(executor) self.tabs.setCurrentWidget(executor)
executor.executionFinished.connect(lambda _: QTimer.singleShot(0, lambda: executor.resetExecution(fromAuto=True)))
def initProcedureManagementTab(self): def initProcedureManagementTab(self):
"""创建规程管理主标签页""" """创建规程管理主标签页"""
mainWidget = QWidget() mainWidget = QWidget()
@ -318,6 +323,17 @@ class ProcedureManager(QMainWindow):
self.historyAction.triggered.connect(self.openHistoryViewer) self.historyAction.triggered.connect(self.openHistoryViewer)
self.toolbar.addAction(self.historyAction) self.toolbar.addAction(self.historyAction)
# 添加批量执行规程动作
self.batchExecuteAction = QAction(
qta.icon('fa5s.play-circle', color='green'),
"批量执行",
self
)
self.batchExecuteAction.setIconText("批量执行")
self.batchExecuteAction.setStatusTip("按顺序批量执行当前分类中的所有规程")
self.batchExecuteAction.triggered.connect(self.batchExecuteProcedures)
self.toolbar.addAction(self.batchExecuteAction)
def loadCategories(self): def loadCategories(self):
self.categoryList.clear() self.categoryList.clear()
categories = self.db.getCategories() categories = self.db.getCategories()
@ -544,3 +560,156 @@ class ProcedureManager(QMainWindow):
deleteAction = menu.addAction("删除规程") deleteAction = menu.addAction("删除规程")
deleteAction.triggered.connect(self.deleteSelectedProcedure) deleteAction.triggered.connect(self.deleteSelectedProcedure)
menu.exec_(self.procedureList.mapToGlobal(pos)) menu.exec_(self.procedureList.mapToGlobal(pos))
# 批量执行相关方法
def batchExecuteProcedures(self):
"""开始批量执行当前分类中的所有规程"""
# 检查是否有正在运行的执行器
if self.hasRunningExecutor():
QMessageBox.warning(
self,
"操作被阻止",
"当前有规程正在执行中,请先停止执行后再开始批量执行。"
)
return
# 获取当前分类的所有规程
currentItem = self.categoryList.currentItem()
if not currentItem:
QMessageBox.warning(self, "未选择分类", "请先选择一个分类")
return
categoryId = currentItem.data(Qt.UserRole)
procedures = self.db.getProcedures(categoryId)
if not procedures:
QMessageBox.information(self, "无规程", "当前分类中没有规程")
return
# 确认批量执行
reply = QMessageBox.question(
self,
"确认批量执行",
f"确定要批量执行当前分类中的 {len(procedures)} 个规程吗?\n"
f"规程将按顺序逐个执行,每个规程执行完毕后自动开始下一个。",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
# 初始化批量执行队列
self.batchExecutionQueue = [(procId, name, number) for procId, name, number, type, createdAt in procedures]
self.currentBatchIndex = 0
self.isBatchExecuting = True
# 更新状态
self.statusBar.showMessage(f"开始批量执行 {len(procedures)} 个规程", 3000)
# 开始执行第一个规程
self.executeNextBatchProcedure()
def executeNextBatchProcedure(self):
"""执行下一个规程"""
print(f"executeNextBatchProcedure: 批量执行={self.isBatchExecuting}, 当前索引={self.currentBatchIndex}, 队列长度={len(self.batchExecutionQueue)}")
if not self.isBatchExecuting or self.currentBatchIndex >= len(self.batchExecutionQueue):
print("批量执行结束条件满足调用finishBatchExecution")
self.finishBatchExecution()
return
# 获取当前要执行的规程
procId, name, number = self.batchExecutionQueue[self.currentBatchIndex]
print(f"准备执行规程: {name} ({number}) - {self.currentBatchIndex + 1}/{len(self.batchExecutionQueue)}")
# 获取规程数据
procedureData = self.db.getProcedureContent(procId)
if not procedureData:
QMessageBox.warning(self, "获取规程失败", f"无法获取规程 {name} 的内容")
self.currentBatchIndex += 1
self.executeNextBatchProcedure()
return
# 创建执行器
executor = StepExecutor(procedureData, procId, self.db)
# 设置规程全名
procName = procedureData["规程信息"]["规程名称"]
procNumber = procedureData["规程信息"]["规程编号"]
tab_title = f"批量执行-{procName}({procNumber})"
# 添加为新标签页
tab_index = self.tabs.addTab(executor, tab_title)
self.currentBatchTabIndex = tab_index # 记录当前标签页索引
print(f"创建标签页: {tab_title}, 索引={tab_index}")
# 连接标签锁定信号
executor.tabLockRequired.connect(lambda lock: self.lockTabs(lock, tab_index))
# 切换到新添加的标签页
self.tabs.setCurrentWidget(executor)
# 开始自动执行
executor.startAutoExecute()
print(f"开始自动执行规程: {procName}")
# 启动批量执行状态检查定时器(如果还没启动)
if not self.batchExecutionTimer.isActive():
self.batchExecutionTimer.start(1000) # 每秒检查一次
print("启动批量执行状态检查定时器")
else:
print("批量执行状态检查定时器已在运行")
# 更新状态
self.statusBar.showMessage(f"正在执行: {name} ({number}) - {self.currentBatchIndex + 1}/{len(self.batchExecutionQueue)}", 0)
def checkBatchExecutionStatus(self):
"""检查批量执行状态"""
if not self.isBatchExecuting:
return
# 检查当前是否有正在运行的执行器
has_running = self.hasRunningExecutor()
print(f"批量执行状态检查: 当前索引={self.currentBatchIndex}, 队列长度={len(self.batchExecutionQueue)}, 有运行执行器={has_running}")
if not has_running:
# 如果没有正在运行的执行器,说明当前规程执行完成
print(f"规程执行完成,准备执行下一个规程")
# 确保当前标签页存在且有效
if self.currentBatchTabIndex > 0 and self.currentBatchTabIndex < self.tabs.count():
# 关闭当前执行器标签页
self.tabs.removeTab(self.currentBatchTabIndex)
self.currentBatchTabIndex = -1 # 重置标签页索引
print(f"已关闭标签页,索引={self.currentBatchTabIndex}")
# 移动到下一个规程
self.currentBatchIndex += 1
print(f"移动到下一个规程,新索引={self.currentBatchIndex}")
if self.currentBatchIndex >= len(self.batchExecutionQueue):
# 所有规程执行完成
print("所有规程执行完成")
self.finishBatchExecution()
else:
# 延迟一下再执行下一个规程,让用户看到执行结果
print(f"延迟2秒后执行下一个规程: {self.currentBatchIndex + 1}/{len(self.batchExecutionQueue)}")
# 停止当前定时器,避免重复检查
self.batchExecutionTimer.stop()
QTimer.singleShot(2000, self.executeNextBatchProcedure)
def finishBatchExecution(self):
"""完成批量执行"""
self.isBatchExecuting = False
self.batchExecutionQueue = []
self.currentBatchIndex = 0
self.currentBatchTabIndex = -1 # 重置标签页索引
self.batchExecutionTimer.stop()
# 更新状态
self.statusBar.showMessage("批量执行完成", 5000)
# 显示完成消息
QMessageBox.information(
self,
"批量执行完成",
"所有规程已按顺序执行完毕!"
)

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

@ -45,7 +45,7 @@ class ExcelParser:
stepCounter += 1 stepCounter += 1
stepName = str(cellA).replace('', ':').strip() stepName = str(cellA).replace('', ':').strip()
stepDesc = str(cellB).replace('\n', ' ').strip() if cellB else "" stepDesc = str(cellB) if cellB else ""
currentStep = { currentStep = {
"步骤ID": stepName, "步骤ID": stepName,
@ -57,7 +57,7 @@ class ExcelParser:
elif currentStep and cellA and str(cellA).isdigit(): elif currentStep and cellA and str(cellA).isdigit():
subStep = { subStep = {
"序号": int(cellA), "序号": int(cellA),
"操作": str(cellB).replace('\n', ' ').strip() if cellB else "", "操作": str(cellB) if cellB else "",
"操作类型": cellC if cellC else "", "操作类型": cellC if cellC else "",
"预期结果": sheet[f'D{rowIdx}'].value, "预期结果": sheet[f'D{rowIdx}'].value,
"实际结果": sheet[f'E{rowIdx}'].value, "实际结果": sheet[f'E{rowIdx}'].value,
@ -153,6 +153,14 @@ class StepTableModel(QAbstractTableModel):
font.setBold(True) font.setBold(True)
return font 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 return None
def headerData(self, section, orientation, role): def headerData(self, section, orientation, role):

@ -3,7 +3,7 @@ from utils.DBModels.ProtocolModel import (
HartVar, TcRtdVar, AnalogVar, HartSimulateVar HartVar, TcRtdVar, AnalogVar, HartSimulateVar
) )
from protocol.TCP.TCPVarManage import TCPVarManager from protocol.TCP.TCPVarManage import *
from protocol.TCP.TemToMv import temToMv from protocol.TCP.TemToMv import temToMv
class ProtocolManage(object): class ProtocolManage(object):
@ -21,6 +21,7 @@ class ProtocolManage(object):
self.writeTC = [0] * 8 self.writeTC = [0] * 8
self.writeRTD = [0] * 8 self.writeRTD = [0] * 8
@classmethod @classmethod
def lookupVariable(cls, variableName): def lookupVariable(cls, variableName):
""" """
@ -91,22 +92,29 @@ class ProtocolManage(object):
channel = int(info['channelNumber']) - 1 channel = int(info['channelNumber']) - 1
varType = info['varType'] varType = info['varType']
compensationVar = float(info['compensationVar']) compensationVar = float(info['compensationVar'])
varModel = info['varModel']
model = self.getModelType(varModel)
# print(value + compensationVar) # print(value + compensationVar)
if varType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A', 'PT100']: if model == localModel:
mvΩvalue = temToMv(varType, value + compensationVar) # 直接补偿温度 补偿mv调整到括号外 if varType == 'PT100':
self.tcpVarManager.writeValue(varType, channel, mvΩvalue) self.writeRTD[channel] = value
if varType == 'PT100': else:
self.wrtieRTD[channel] = value self.writeTC[channel] = value
else: if varType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A', 'PT100'] and model != SimModel:
self.writeTC[channel] = value value = temToMv(varType, value + compensationVar) # 直接补偿温度 补偿mv调整到括号外
self.tcpVarManager.writeValue(varType, channel, value, model=model)
# 模拟量变量处理 # 模拟量变量处理
elif modelType == 'AnalogVar': elif modelType == 'AnalogVar':
channel = int(info['channelNumber']) - 1 channel = int(info['channelNumber']) - 1
varType = info['varType'] varType = info['varType']
if info['varType'] == 'AO': varModel = info['varModel']
model = self.getModelType(varModel)
if info['varType'] in ['AI','AO']:
value = self.getRealAO(value, info['max'], info['min']) value = self.getRealAO(value, info['max'], info['min'])
self.tcpVarManager.writeValue(varType, channel, value) self.tcpVarManager.writeValue(varType, channel, value, model=model)
# print(1) # print(1)
# HART模拟变量处理 # HART模拟变量处理
@ -157,18 +165,29 @@ class ProtocolManage(object):
elif modelType == 'TcRtdVar': elif modelType == 'TcRtdVar':
channel = int(info['channelNumber']) - 1 channel = int(info['channelNumber']) - 1
varType = info['varType'] varType = info['varType']
if varType == 'PT100': varModel = info['varModel']
value = self.writeRTD[channel] model = self.getModelType(varModel)
elif varType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A']: if model == SimModel:
value = self.writeTC[channel] if varType == 'PT100':
return value value = self.tcpVarManager.simRTDData[channel]
elif varType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A']:
value = self.tcpVarManager.simTCData[channel]
return value
else:
if varType == 'PT100':
value = self.writeRTD[channel]
elif varType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A']:
value = self.writeTC[channel]
return value
# 模拟量变量处理 # 模拟量变量处理
elif modelType == 'AnalogVar': elif modelType == 'AnalogVar':
channel = int(info['channelNumber']) - 1 channel = int(info['channelNumber']) - 1
varType = info['varType'] varType = info['varType']
# print(varType, channel) # print(varType, channel)
value = self.tcpVarManager.readValue(varType, channel) varModel = info['varModel']
model = self.getModelType(varModel)
value = self.tcpVarManager.readValue(varType, channel, model=model)
if varType in ['AI','AO']: if varType in ['AI','AO']:
value = self.getRealAI(value, info['max'], info['min']) value = self.getRealAI(value, info['max'], info['min'])
return value return value
@ -215,3 +234,11 @@ class ProtocolManage(object):
except Exception as e: except Exception as e:
print(f"工程值转换失败: {str(e)}") print(f"工程值转换失败: {str(e)}")
return 0.0 # 默认返回0避免中断流程 return 0.0 # 默认返回0避免中断流程
def getModelType(self, varModel):
if varModel == '本地值':
return localModel
elif varModel == '远程值':
return NetModel
elif varModel == '模拟值':
return SimModel

@ -7,6 +7,10 @@ FPGATrigger = 1
TCTrigger = 2 TCTrigger = 2
RTDTrigger = 3 RTDTrigger = 3
localModel = 1
NetModel = 2
SimModel = 3
class TCPVarManager: class TCPVarManager:
def __init__(self, host, port): def __init__(self, host, port):
@ -24,6 +28,13 @@ class TCPVarManager:
self.AIDATA = [0.0] * 8 self.AIDATA = [0.0] * 8
self.DIDATA = [0] * 16 self.DIDATA = [0] * 16
self.DELTATDATA = [0] * 16 self.DELTATDATA = [0] * 16
self.simAOData = [0.004] * 16
self.simDOData = [0] * 16
self.simTCData = [0.0] * 8 # mv
self.simRTDData = [0.0] * 8 # Ω
self.simAIata = [0.0] * 8
self.simDIata = [0] * 16
self.startPeriodicRead() self.startPeriodicRead()
def startTimeTest(self, triggerType, time = 2000): def startTimeTest(self, triggerType, time = 2000):
@ -122,12 +133,12 @@ class TCPVarManager:
try: try:
# 将DO状态转换为单个浮点数协议要求 # 将DO状态转换为单个浮点数协议要求
do_value = 0 doValue = 0
for i, state in enumerate(self.DODATA): for i, state in enumerate(self.DODATA):
do_value |= (state << i) doValue |= (state << i)
# 替换data中第20个元素为DO状态值 # 替换data中第20个元素为DO状态值
data[19] = float(do_value) data[19] = float(doValue)
success = self.communicator.writeAo(data) success = self.communicator.writeAo(data)
if not success: if not success:
@ -152,15 +163,39 @@ class TCPVarManager:
return None return None
def writeValue(self, variableType, channel, value, trigger=None): def writeValue(self, variableType, channel, value, trigger=None, model = localModel):
if variableType == "AO": if variableType == "AO":
self.AODATA[channel] = float(value) if model == SimModel:
self.simAOData[channel] = float(value)
return
else:
self.AODATA[channel] = float(value)
elif variableType == "DO": elif variableType == "DO":
self.DODATA[channel] = int(value) if model == SimModel:
self.simDOData[channel] = int(value)
return
else:
self.DODATA[channel] = int(value)
elif variableType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A']: elif variableType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A']:
self.TCDATA[channel] = float(value) if model == SimModel:
self.simTCData[channel] = float(value)
return
else:
self.TCDATA[channel] = float(value)
elif variableType == "PT100": elif variableType == "PT100":
self.RTDDATA[channel] = float(value) if model == SimModel:
self.simRTDData[channel] = float(value)
return
else:
self.RTDDATA[channel] = float(value)
elif variableType == "AI":
if model == SimModel:
self.simAIata[channel] = float(value)
return
elif variableType == "DI":
if model == SimModel:
self.simDIata[channel] = int(value)
return
if not trigger: if not trigger:
data = [ data = [
@ -188,17 +223,29 @@ class TCPVarManager:
# print(data) # print(data)
self.writeData(data) self.writeData(data)
def readValue(self, variableType, channel): def readValue(self, variableType, channel, model = localModel):
# channel = channel # channel = channel
if variableType == "AI": if variableType == "AI":
# print(self.AIDATA) # print(self.AIDATA)
return self.AIDATA[channel] if model == SimModel:
return self.simAIata[channel]
else:
return self.AIDATA[channel]
elif variableType == "DI": elif variableType == "DI":
return self.DIDATA[channel] if model == SimModel:
return self.simDIata[channel]
else:
return self.DIDATA[channel]
elif variableType == "AO": elif variableType == "AO":
return self.AODATA[channel] if model == SimModel:
return self.simAOData[channel]
else:
return self.AODATA[channel]
elif variableType == "DO": elif variableType == "DO":
return self.DODATA[channel] if model == SimModel:
return self.simDOData[channel]
else:
return self.DODATA[channel]

Loading…
Cancel
Save