diff --git a/UI/ProcedureManager/HistoryViewer.py b/UI/ProcedureManager/HistoryViewer.py index abc86a4..e98c456 100644 --- a/UI/ProcedureManager/HistoryViewer.py +++ b/UI/ProcedureManager/HistoryViewer.py @@ -1,32 +1,21 @@ import sys import os -from PyQt5.QtCore import Qt, QTimer, QAbstractTableModel, QModelIndex, QPoint, QSize, pyqtSignal, QFile, QTextStream -from PyQt5.QtGui import QBrush, QColor, QFont, QStandardItemModel, QStandardItem -from PyQt5.QtWidgets import (QApplication, QMainWindow, QTableView, - QPushButton, QVBoxLayout, QWidget, QHBoxLayout, - QLabel, QCheckBox, QSpinBox, QMenu, QFileDialog, - QTabWidget, QTabBar, QListWidget, QListWidgetItem, QDialog, - QLineEdit, QFormLayout, QDialogButtonBox, QMessageBox, - QHeaderView, QToolBar, QAction, QStatusBar, QComboBox, QSplitter, QAbstractItemView) -from docx import Document -from docx.shared import Pt, RGBColor -from docx.enum.text import WD_PARAGRAPH_ALIGNMENT +from PyQt5.QtCore import Qt, QTimer, QModelIndex, QSize, pyqtSignal, QFile, QTextStream +from PyQt5.QtGui import QBrush, QColor, QStandardItemModel, QStandardItem +from PyQt5.QtWidgets import (QTableView, QPushButton, QVBoxLayout, QWidget, QHBoxLayout, + QLabel, QMenu, QFileDialog, QDialog, QLineEdit, + QDialogButtonBox, QMessageBox, QHeaderView, QSplitter) import qtawesome as qta - from datetime import datetime -# 导入其他模块 -from model.ProcedureModel.ProcedureProcessor import ExcelParser, StepTableModel from utils.DBModels.ProcedureModel import DatabaseManager -from UI.ProcedureManager.StepExecutor import StepExecutor # 修改导入路径 +from UI.ProcedureManager.StepExecutor import StepExecutor -class HistoryViewerWidget(QDialog): +class HistoryViewerWidget(QWidget): def __init__(self, dbManager, parent=None): super().__init__(parent) self.dbManager = dbManager - self.setWindowTitle("历史记录查看器") - self.setGeometry(200, 200, 1000, 800) self.initUi() self.loadHistory() @@ -41,8 +30,7 @@ class HistoryViewerWidget(QDialog): self.searchEdit.textChanged.connect(self.loadHistory) searchLayout.addWidget(QLabel("搜索:")) searchLayout.addWidget(self.searchEdit) - # 将搜索栏布局添加到主布局 - layout.addLayout(searchLayout) # 修复:在此处添加搜索栏 + layout.addLayout(searchLayout) # 历史记录表格 self.table = QTableView() @@ -52,81 +40,122 @@ class HistoryViewerWidget(QDialog): self.table.setModel(self.model) self.table.doubleClicked.connect(self.openReportOrDetails) self.table.setSelectionBehavior(QTableView.SelectRows) - # 新增:设置表格的右键菜单策略 self.table.setContextMenuPolicy(Qt.CustomContextMenu) self.table.customContextMenuRequested.connect(self.showHistoryContextMenu) - - # 新增:设置表格列宽自适应内容 - self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) + self.setupTableDisplay(self.table) # 步骤详情表格 self.stepTable = QTableView() - self.stepTable.setEditTriggers(QTableView.NoEditTriggers) # 新增:禁用编辑 + self.stepTable.setEditTriggers(QTableView.NoEditTriggers) self.stepModel = QStandardItemModel() self.stepModel.setHorizontalHeaderLabels(["步骤ID", "步骤描述", "执行时间", "执行结果"]) self.stepTable.setModel(self.stepModel) - - # 新增:设置表格列宽自适应内容 - self.stepTable.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) + self.setupTableDisplay(self.stepTable) # 分割窗口 splitter = QSplitter(Qt.Vertical) splitter.addWidget(self.table) splitter.addWidget(self.stepTable) - splitter.setSizes([400, 400]) # 初始分配高度 - + splitter.setSizes([400, 400]) layout.addWidget(splitter) - # 新增:操作按钮布局 - button_layout = QHBoxLayout() + + # 操作按钮 + buttonLayout = QHBoxLayout() + + separator = QWidget() + separator.setFixedWidth(20) + buttonLayout.addWidget(separator) + self.deleteButton = QPushButton("删除历史记录") self.deleteButton.setIcon(qta.icon('fa5s.trash', color='red')) self.deleteButton.clicked.connect(self.deleteSelectedHistory) - button_layout.addWidget(self.deleteButton) + buttonLayout.addWidget(self.deleteButton) - # 新增:导出报告按钮 self.exportButton = QPushButton("导出报告") self.exportButton.setIcon(qta.icon('fa5s.file-export', color='green')) self.exportButton.clicked.connect(self.exportReport) - button_layout.addWidget(self.exportButton) + buttonLayout.addWidget(self.exportButton) - button_layout.addStretch() - layout.addLayout(button_layout) - - # 按钮 - buttonBox = QDialogButtonBox(QDialogButtonBox.Ok) - buttonBox.accepted.connect(self.accept) - layout.addWidget(buttonBox) + buttonLayout.addStretch() + layout.addLayout(buttonLayout) self.setLayout(layout) + def setupTableDisplay(self, table): + """设置表格显示属性""" + table.setWordWrap(True) + table.setAlternatingRowColors(True) + table.setSortingEnabled(True) + + header = table.horizontalHeader() + if header: + header.setStretchLastSection(True) + header.setSectionResizeMode(QHeaderView.Interactive) + header.setDefaultAlignment(Qt.AlignLeft) + + verticalHeader = table.verticalHeader() + if verticalHeader: + verticalHeader.setSectionResizeMode(QHeaderView.ResizeToContents) + verticalHeader.setDefaultAlignment(Qt.AlignCenter) + + if table == self.table: + table.setSelectionMode(QTableView.ExtendedSelection) + else: + table.setSelectionMode(QTableView.SingleSelection) + + table.setSelectionBehavior(QTableView.SelectRows) + + table.setStyleSheet(""" + QTableView { + gridline-color: #d0d0d0; + background-color: white; + alternate-background-color: #f5f5f5; + selection-background-color: #0078d4; + selection-color: white; + } + QTableView::item { + padding: 5px; + border: none; + } + QTableView::item:selected { + background-color: #0078d4; + color: white; + } + QHeaderView::section { + background-color: #f0f0f0; + padding: 5px; + border: 1px solid #d0d0d0; + font-weight: bold; + } + """) + def loadHistory(self): + """加载历史记录""" self.model.removeRows(0, self.model.rowCount()) filterText = self.searchEdit.text().strip() history = self.dbManager.getExecutionHistory(filterText) for record in history: - # 修改:使用正确的列索引(0-4) - row = [ - QStandardItem(str(record[0])), # ID - QStandardItem(record[1]), # 规程全名(名称+编号) - QStandardItem(record[2]), # 类型 - QStandardItem(record[3]), # 执行时间 - QStandardItem(record[4]) # 报告路径 - ] - self.model.appendRow(row) - - self.table.resizeColumnsToContents() + rowItems = [] + for i, value in enumerate(record): + item = QStandardItem(str(value) if value is not None else "") + item.setTextAlignment(Qt.AlignTop | Qt.AlignLeft) + if i in [1, 4]: # 规程全名和报告路径列 + item.setToolTip(str(value) if value is not None else "") + rowItems.append(item) + self.model.appendRow(rowItems) + + self.adjustTableColumns(self.table, self.model) def openReportOrDetails(self, index): - # 修改:将 get_step_results 改为 getStepResults - execution_id = self.model.item(index.row(), 0).text() - step_results = self.dbManager.getStepResults(execution_id) # 修正方法名 - self.showStepDetails(step_results) - # 保存操作历史 - self.saveOperationHistory(execution_id, "查看步骤详情", "") + """打开报告或详情""" + executionId = self.model.item(index.row(), 0).text() + stepResults = self.dbManager.getStepResults(executionId) + self.showStepDetails(stepResults) + self.saveOperationHistory(executionId, "查看步骤详情", "") def saveOperationHistory(self, executionId, operationType, operationDetail): - """保存操作历史记录""" + """保存操作历史""" self.dbManager.saveOperationHistory( executionId, operationType, @@ -141,104 +170,178 @@ class HistoryViewerWidget(QDialog): return for step in stepResults: - row = [ - QStandardItem(step['step_id']), - QStandardItem(step['step_description']), - QStandardItem(step['execution_time']), - QStandardItem('成功' if step['result'] else '失败') - ] - self.stepModel.appendRow(row) + rowItems = [] + + # 步骤ID + stepIdItem = QStandardItem(step.get('step_id', '')) + stepIdItem.setTextAlignment(Qt.AlignCenter) + rowItems.append(stepIdItem) + + # 步骤描述 + descItem = QStandardItem(step.get('step_description', '')) + descItem.setTextAlignment(Qt.AlignTop | Qt.AlignLeft) + descItem.setToolTip(step.get('step_description', '')) + rowItems.append(descItem) + + # 执行时间 + timeItem = QStandardItem(step.get('execution_time', '')) + timeItem.setTextAlignment(Qt.AlignCenter) + rowItems.append(timeItem) + + # 执行结果 + resultText = '成功' if step.get('result', False) else '失败' + resultItem = QStandardItem(resultText) + resultItem.setTextAlignment(Qt.AlignCenter) + if step.get('result', False): + resultItem.setBackground(QBrush(QColor(200, 255, 200))) + else: + resultItem.setBackground(QBrush(QColor(255, 200, 200))) + rowItems.append(resultItem) + + self.stepModel.appendRow(rowItems) + + self.adjustTableColumns(self.stepTable, self.stepModel) + + def adjustTableColumns(self, table, model): + """调整表格列宽""" + header = table.horizontalHeader() + if not header: + return + + for col in range(model.columnCount()): + if col == 0: # ID列 + header.setSectionResizeMode(col, QHeaderView.ResizeToContents) + elif col == 1: # 描述列 + header.setSectionResizeMode(col, QHeaderView.Stretch) + elif col == 2: # 类型列 + header.setSectionResizeMode(col, QHeaderView.ResizeToContents) + elif col == 3: # 时间列 + header.setSectionResizeMode(col, QHeaderView.ResizeToContents) + elif col == 4: # 报告路径或结果列 + if model == self.model: + header.setSectionResizeMode(col, QHeaderView.Stretch) + else: + header.setSectionResizeMode(col, QHeaderView.ResizeToContents) + + table.resizeRowsToContents() - # 新增删除历史记录的方法 def deleteSelectedHistory(self): """删除选中的历史记录""" - selected_indexes = self.table.selectionModel().selectedRows() - if not selected_indexes: + selectedIndexes = self.table.selectionModel().selectedRows() + if not selectedIndexes: QMessageBox.warning(self, "未选择", "请先选择要删除的历史记录") return - # 获取选中的执行ID - execution_ids = [] - for index in selected_indexes: - execution_id = self.model.item(index.row(), 0).text() - execution_ids.append(execution_id) + executionIds = [] + for index in selectedIndexes: + executionId = self.model.item(index.row(), 0).text() + executionIds.append(executionId) - # 确认删除 reply = QMessageBox.question( self, "确认删除", - f"确定要删除选中的 {len(execution_ids)} 条历史记录吗?\n此操作不可恢复!", + f"确定要删除选中的 {len(executionIds)} 条历史记录吗?\n此操作不可恢复!", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: - # 修改:将 delete_execution_history 改为 deleteExecutionHistory - success = self.dbManager.deleteExecutionHistory(execution_ids) # 修正方法名 + success = self.dbManager.deleteExecutionHistory(executionIds) if success: QMessageBox.information(self, "删除成功", "已成功删除选中的历史记录") - self.loadHistory() # 重新加载历史记录 + self.loadHistory() else: QMessageBox.warning(self, "删除失败", "删除历史记录时发生错误") def exportReport(self): - """导出选中历史记录的报告""" - selected_indexes = self.table.selectionModel().selectedRows() - if not selected_indexes: + """导出报告""" + selectedIndexes = self.table.selectionModel().selectedRows() + if not selectedIndexes: QMessageBox.warning(self, "未选择", "请先选择要导出的历史记录") return - if len(selected_indexes) > 1: + if len(selectedIndexes) > 1: QMessageBox.warning(self, "选择过多", "一次只能导出一个历史记录的报告") return - index = selected_indexes[0] - execution_id = self.model.item(index.row(), 0).text() - # 修改:报告路径现在是第5列(索引4) - reportPath = self.model.item(index.row(), 4).text() + index = selectedIndexes[0] + executionId = self.model.item(index.row(), 0).text() - # 获取执行详情数据 - # 修改:将 get_execution_details 改为 getExecutionDetails - execution_data = self.dbManager.getExecutionDetails(execution_id) # 修正方法名 - if not execution_data: + executionData = self.dbManager.getExecutionDetails(executionId) + if not executionData: QMessageBox.warning(self, "数据错误", "无法获取执行详情数据") return - # 生成报告 try: - # 创建临时StepExecutor实例用于生成报告 - executor = StepExecutor(execution_data['procedure_content'], - execution_data['procedure_id'], + executor = StepExecutor(executionData['procedure_content'], + executionData['procedure_id'], self.dbManager) - executor.stepResults = execution_data['step_results'] - # 设置步骤数据 + executor.stepResults = executionData['step_results'] + for step in executor.tableModel.stepData: - step_result = next((s for s in execution_data['step_results'] + stepResult = next((s for s in executionData['step_results'] if s['step_id'] == step['stepId']), None) - if step_result: + if stepResult: step['executed'] = True - step['result'] = step_result['result'] - step['time'] = datetime.strptime(step_result['execution_time'], "%Y-%m-%d %H:%M:%S") - - # 生成报告文件 - default_name = f"{execution_data['procedure_name']}_历史报告_{execution_id}.docx" - # file_path, _ = QFileDialog.getSaveFileName( - # self, "保存报告", default_name, "Word文档 (*.docx)" - # ) - - # if file_path: - res = executor.generateReport() - # QMessageBox.information(self, "导出成功", f"报告已保存到:\n{res}") + step['result'] = stepResult['result'] + step['time'] = datetime.strptime(stepResult['execution_time'], "%Y-%m-%d %H:%M:%S") + + resultPath = executor.generateReport() + + if resultPath: + return + else: + QMessageBox.information(self, "导出取消", "用户取消了报告导出") except Exception as e: QMessageBox.critical(self, "导出错误", f"生成报告时出错:\n{str(e)}") + import traceback + print(f"导出报告详细错误: {traceback.format_exc()}") - # 新增:历史记录表格的右键菜单 def showHistoryContextMenu(self, pos): - """显示历史记录的右键菜单""" + """显示右键菜单""" index = self.table.indexAt(pos) + menu = QMenu() + if index.isValid(): - menu = QMenu() - deleteAction = menu.addAction("删除历史记录") + deleteAction = menu.addAction(qta.icon('fa5s.trash', color='red'), "删除历史记录") deleteAction.triggered.connect(self.deleteSelectedHistory) - menu.exec_(self.table.viewport().mapToGlobal(pos)) \ No newline at end of file + menu.addSeparator() + + exportAction = menu.addAction(qta.icon('fa5s.file-export', color='green'), "导出报告") + exportAction.triggered.connect(lambda: self.exportReportFromContextMenu(index)) + menu.addSeparator() + + selectAllAction = menu.addAction("全选") + selectAllAction.triggered.connect(self.selectAllHistory) + + deselectAllAction = menu.addAction("取消全选") + deselectAllAction.triggered.connect(self.deselectAllHistory) + + if index.isValid(): + menu.exec_(self.table.viewport().mapToGlobal(pos)) + else: + menu.exec_(self.table.mapToGlobal(pos)) + + def showEvent(self, event): + """窗口显示事件""" + super().showEvent(event) + QTimer.singleShot(100, self.adjustTablesAfterShow) + + def adjustTablesAfterShow(self): + """窗口显示后调整表格""" + self.adjustTableColumns(self.table, self.model) + self.adjustTableColumns(self.stepTable, self.stepModel) + + def selectAllHistory(self): + """全选历史记录""" + self.table.selectAll() + + def deselectAllHistory(self): + """取消全选历史记录""" + self.table.clearSelection() + + def exportReportFromContextMenu(self, index): + """从右键菜单导出报告""" + self.table.selectRow(index.row()) + self.exportReport() \ No newline at end of file diff --git a/UI/ProcedureManager/KeywordManager.py b/UI/ProcedureManager/KeywordManager.py new file mode 100644 index 0000000..2c931a2 --- /dev/null +++ b/UI/ProcedureManager/KeywordManager.py @@ -0,0 +1,338 @@ +import sys +from PyQt5 import QtCore +from PyQt5.QtCore import QAbstractTableModel, QModelIndex, pyqtSignal, QSize +from PyQt5.QtGui import QFont, QBrush, QColor +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QTableView, + QPushButton, QLabel, QLineEdit, QComboBox, + QDialog, QFormLayout, QDialogButtonBox, QMessageBox, + QHeaderView, QMenu, QAction, QToolBar, QSpinBox) +import qtawesome as qta +from datetime import datetime + + +class KeywordTableModel(QAbstractTableModel): + """关键词表格模型""" + columns = ['ID', '关键词', '操作类型', '描述', '创建时间'] + + def __init__(self, dbManager): + super().__init__() + self.dbManager = dbManager + self.keywordData = [] + self.loadData() + + def loadData(self): + """加载数据""" + self.beginResetModel() + self.keywordData = self.dbManager.getStepKeywords() + self.endResetModel() + + def rowCount(self, parent=QModelIndex()): + return len(self.keywordData) + + def columnCount(self, parent=QModelIndex()): + return len(self.columns) + + def data(self, index, role=QtCore.Qt.DisplayRole): + if not index.isValid(): + return None + + row = index.row() + col = index.column() + + if row >= len(self.keywordData): + return None + + keywordInfo = self.keywordData[row] + + if role == QtCore.Qt.DisplayRole: + if col == 0: + return str(keywordInfo[0]) # ID + elif col == 1: + return keywordInfo[1] # 关键词 + elif col == 2: + return keywordInfo[2] # 操作类型 + elif col == 3: + return keywordInfo[3] or "" # 描述 + elif col == 4: + return keywordInfo[4] # 创建时间 + + elif role == QtCore.Qt.BackgroundRole: + if row % 2 == 0: + return QBrush(QColor(245, 245, 245)) + else: + return QBrush(QColor(255, 255, 255)) + + elif role == QtCore.Qt.FontRole: + font = QFont() + if col == 1: # 关键词列加粗 + font.setBold(True) + return font + + return None + + def headerData(self, section, orientation, role): + if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal: + return self.columns[section] + return None + + def getKeywordInfo(self, row): + """获取指定行的关键词信息""" + if 0 <= row < len(self.keywordData): + return { + 'id': self.keywordData[row][0], + 'keyword': self.keywordData[row][1], + 'operationType': self.keywordData[row][2], + 'description': self.keywordData[row][3], + 'createdAt': self.keywordData[row][4] + } + return None + + +class KeywordEditDialog(QDialog): + """关键词编辑对话框""" + def __init__(self, parent=None, keywordInfo=None): + super().__init__(parent) + self.keywordInfo = keywordInfo + self.isEdit = keywordInfo is not None + + self.setWindowTitle("编辑关键词" if self.isEdit else "添加关键词") + self.setFixedSize(400, 200) + self.setupUi() + + if self.isEdit: + self.loadKeywordInfo() + + def setupUi(self): + """设置界面""" + layout = QVBoxLayout() + + formLayout = QFormLayout() + + self.keywordEdit = QLineEdit() + self.keywordEdit.setPlaceholderText("请输入关键词") + formLayout.addRow("关键词:", self.keywordEdit) + + self.operationTypeCombo = QComboBox() + self.operationTypeCombo.addItems(['set', 'check', 'wait', 'deltaT']) + formLayout.addRow("操作类型:", self.operationTypeCombo) + + self.descriptionEdit = QLineEdit() + self.descriptionEdit.setPlaceholderText("请输入描述(可选)") + formLayout.addRow("描述:", self.descriptionEdit) + + layout.addLayout(formLayout) + + buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + buttonBox.accepted.connect(self.accept) + buttonBox.rejected.connect(self.reject) + layout.addWidget(buttonBox) + + self.setLayout(layout) + + def loadKeywordInfo(self): + """加载关键词信息""" + if self.keywordInfo: + self.keywordEdit.setText(self.keywordInfo['keyword']) + self.operationTypeCombo.setCurrentText(self.keywordInfo['operationType']) + self.descriptionEdit.setText(self.keywordInfo['description'] or "") + + def getKeywordData(self): + """获取关键词数据""" + return { + 'keyword': self.keywordEdit.text().strip(), + 'operationType': self.operationTypeCombo.currentText(), + 'description': self.descriptionEdit.text().strip() + } + + +class KeywordManagerWidget(QWidget): + """关键词字段库管理界面""" + def __init__(self, dbManager, parent=None): + super().__init__(parent) + self.dbManager = dbManager + self.setupUi() + self.setupToolbar() + self.setupContextMenu() + + def setupUi(self): + """设置界面""" + layout = QVBoxLayout() + + # 创建工具栏 + self.toolbar = QToolBar("关键词管理工具栏") + self.toolbar.setIconSize(QSize(24, 24)) + layout.addWidget(self.toolbar) + + # 创建表格 + self.tableModel = KeywordTableModel(self.dbManager) + self.tableView = QTableView() + self.tableView.setModel(self.tableModel) + self.tableView.setSelectionBehavior(QTableView.SelectRows) + self.tableView.setSelectionMode(QTableView.SingleSelection) + self.tableView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.tableView.customContextMenuRequested.connect(self.showContextMenu) + + # 设置表格样式 + self.setupTableStyle() + + layout.addWidget(self.tableView) + + # 创建状态栏 + self.statusLabel = QLabel("就绪") + layout.addWidget(self.statusLabel) + + self.setLayout(layout) + + def setupToolbar(self): + """设置工具栏""" + # 添加关键词 + addAction = QAction(qta.icon('fa5s.plus', color='green'), "添加关键词", self) + addAction.triggered.connect(self.addKeyword) + self.toolbar.addAction(addAction) + + # 编辑关键词 + editAction = QAction(qta.icon('fa5s.edit', color='blue'), "编辑关键词", self) + editAction.triggered.connect(self.editKeyword) + self.toolbar.addAction(editAction) + + # 删除关键词 + deleteAction = QAction(qta.icon('fa5s.trash', color='red'), "删除关键词", self) + deleteAction.triggered.connect(self.deleteKeyword) + self.toolbar.addAction(deleteAction) + + self.toolbar.addSeparator() + + # 刷新 + refreshAction = QAction(qta.icon('fa5s.sync', color='orange'), "刷新", self) + refreshAction.triggered.connect(self.refreshData) + self.toolbar.addAction(refreshAction) + + def setupTableStyle(self): + """设置表格样式""" + header = self.tableView.horizontalHeader() + if header: + header.setSectionResizeMode(0, QHeaderView.ResizeToContents) # ID + header.setSectionResizeMode(1, QHeaderView.Stretch) # 关键词 + header.setSectionResizeMode(2, QHeaderView.ResizeToContents) # 操作类型 + header.setSectionResizeMode(3, QHeaderView.Stretch) # 描述 + header.setSectionResizeMode(4, QHeaderView.ResizeToContents) # 创建时间 + + # 设置交替行颜色 + self.tableView.setAlternatingRowColors(True) + + # 设置表格不可编辑 + self.tableView.setEditTriggers(QTableView.NoEditTriggers) + + def setupContextMenu(self): + """设置右键菜单""" + self.tableView.customContextMenuRequested.connect(self.showContextMenu) + + def showContextMenu(self, pos): + """显示右键菜单""" + index = self.tableView.indexAt(pos) + if not index.isValid(): + return + + menu = QMenu() + + editAction = menu.addAction("编辑关键词") + editAction.triggered.connect(self.editKeyword) + + deleteAction = menu.addAction("删除关键词") + deleteAction.triggered.connect(self.deleteKeyword) + + if hasattr(self.tableView, 'viewport') and hasattr(self.tableView.viewport(), 'mapToGlobal'): + menu.exec_(self.tableView.viewport().mapToGlobal(pos)) + + def addKeyword(self): + """添加关键词""" + dialog = KeywordEditDialog(self) + if dialog.exec_() == QDialog.Accepted: + keywordData = dialog.getKeywordData() + + if not keywordData['keyword']: + QMessageBox.warning(self, "输入错误", "关键词不能为空") + return + + keywordId = self.dbManager.addStepKeyword( + keywordData['keyword'], + keywordData['operationType'], + keywordData['description'] + ) + + if keywordId: + self.tableModel.loadData() + self.statusLabel.setText(f"成功添加关键词: {keywordData['keyword']}") + else: + QMessageBox.warning(self, "添加失败", "关键词已存在或添加失败") + + def editKeyword(self): + """编辑关键词""" + currentRow = self.tableView.currentIndex().row() + if currentRow < 0: + QMessageBox.information(self, "提示", "请先选择要编辑的关键词") + return + + keywordInfo = self.tableModel.getKeywordInfo(currentRow) + if not keywordInfo: + return + + dialog = KeywordEditDialog(self, keywordInfo) + if dialog.exec_() == QDialog.Accepted: + keywordData = dialog.getKeywordData() + + if not keywordData['keyword']: + QMessageBox.warning(self, "输入错误", "关键词不能为空") + return + + success = self.dbManager.updateStepKeyword( + keywordInfo['id'], + keywordData['keyword'], + keywordData['operationType'], + keywordData['description'] + ) + + if success: + self.tableModel.loadData() + self.statusLabel.setText(f"成功更新关键词: {keywordData['keyword']}") + else: + QMessageBox.warning(self, "更新失败", "关键词已存在或更新失败") + + def deleteKeyword(self): + """删除关键词""" + currentRow = self.tableView.currentIndex().row() + if currentRow < 0: + QMessageBox.information(self, "提示", "请先选择要删除的关键词") + return + + keywordInfo = self.tableModel.getKeywordInfo(currentRow) + if not keywordInfo: + return + + reply = QMessageBox.question( + self, + "确认删除", + f"确定要删除关键词 '{keywordInfo['keyword']}' 吗?", + QMessageBox.Yes | QMessageBox.No + ) + + if reply == QMessageBox.Yes: + success = self.dbManager.deleteStepKeyword(keywordInfo['id']) + if success: + self.tableModel.loadData() + self.statusLabel.setText(f"成功删除关键词: {keywordInfo['keyword']}") + else: + QMessageBox.warning(self, "删除失败", "删除关键词失败") + + def refreshData(self): + """刷新数据""" + self.tableModel.loadData() + self.statusLabel.setText("数据已刷新") + + def getKeywordByText(self, text): + """根据文本获取关键词""" + return self.dbManager.getKeywordByText(text) + + def getOperationTypes(self): + """获取所有操作类型""" + return self.dbManager.getOperationTypes() \ No newline at end of file diff --git a/UI/ProcedureManager/ProcedureManager.py b/UI/ProcedureManager/ProcedureManager.py index c1202d9..039d53e 100644 --- a/UI/ProcedureManager/ProcedureManager.py +++ b/UI/ProcedureManager/ProcedureManager.py @@ -20,6 +20,7 @@ from model.ProcedureModel.ProcedureProcessor import ExcelParser, StepTableModel from utils.DBModels.ProcedureModel import DatabaseManager from UI.ProcedureManager.HistoryViewer import HistoryViewerWidget from UI.ProcedureManager.StepExecutor import StepExecutor # 修改导入路径 +from UI.ProcedureManager.KeywordManager import KeywordManagerWidget # 新增导入 class ExecutionDetailDialog(QDialog): @@ -209,7 +210,8 @@ class ProcedureManager(QMainWindow): # 切换到新添加的标签页 self.tabs.setCurrentWidget(executor) - executor.executionFinished.connect(lambda _: QTimer.singleShot(0, lambda: executor.resetExecution(fromAuto=True))) + # 修改:只在多轮次执行完成时自动重置,单轮次执行完成时不重置 + executor.executionFinished.connect(lambda executor_instance: self.handleExecutionFinished(executor_instance)) def initProcedureManagementTab(self): """创建规程管理主标签页""" @@ -334,6 +336,17 @@ class ProcedureManager(QMainWindow): self.batchExecuteAction.triggered.connect(self.batchExecuteProcedures) self.toolbar.addAction(self.batchExecuteAction) + # 添加关键词管理动作 + self.keywordManageAction = QAction( + qta.icon('fa5s.key', color='purple'), + "关键词管理", + self + ) + self.keywordManageAction.setIconText("关键词管理") + self.keywordManageAction.setStatusTip("管理执行步骤关键词字段库") + self.keywordManageAction.triggered.connect(self.openKeywordManager) + self.toolbar.addAction(self.keywordManageAction) + def loadCategories(self): self.categoryList.clear() categories = self.db.getCategories() @@ -471,8 +484,14 @@ class ProcedureManager(QMainWindow): # 新增:打开历史记录查看器 def openHistoryViewer(self): + """打开历史记录查看器""" historyViewer = HistoryViewerWidget(self.db, self) - historyViewer.exec_() + + # 创建新标签页 + tabIndex = self.tabs.addTab(historyViewer, "历史记录") + self.tabs.setCurrentIndex(tabIndex) + + self.statusBar.showMessage("已打开历史记录查看器", 3000) def loadStylesheet(self): qssPath = "Static/Procedure.qss" @@ -713,3 +732,24 @@ class ProcedureManager(QMainWindow): "批量执行完成", "所有规程已按顺序执行完毕!" ) + + def handleExecutionFinished(self, executor_instance): + """处理执行完成事件""" + # 检查是否为多轮次执行 + if executor_instance.infiniteCycles or executor_instance.remainingCycles > 1: + # 多轮次执行完成,自动重置 + QTimer.singleShot(0, lambda: executor_instance.resetExecution(fromAuto=True)) + else: + # 单轮次执行完成,不自动重置,让用户手动控制 + # 可以在这里添加其他处理逻辑,比如显示完成提示等 + pass + + def openKeywordManager(self): + """打开关键词管理界面""" + keywordManager = KeywordManagerWidget(self.db, self) + + # 创建新标签页 + tabIndex = self.tabs.addTab(keywordManager, "关键词管理") + self.tabs.setCurrentIndex(tabIndex) + + self.statusBar.showMessage("已打开关键词管理界面", 3000) diff --git a/UI/ProcedureManager/StepExecutor.py b/UI/ProcedureManager/StepExecutor.py index 5924dd5..518b8e6 100644 --- a/UI/ProcedureManager/StepExecutor.py +++ b/UI/ProcedureManager/StepExecutor.py @@ -1,99 +1,128 @@ import sys import os -from PyQt5.QtCore import Qt, QTimer, QAbstractTableModel, QModelIndex, QPoint, QSize, pyqtSignal, QFile,QTextStream -from PyQt5.QtGui import QBrush, QColor, QFont, QStandardItemModel, QStandardItem -from PyQt5.QtWidgets import (QApplication, QMainWindow, QTableView, - QPushButton, QVBoxLayout, QWidget, QHBoxLayout, - QLabel, QCheckBox, QSpinBox, QMenu, QFileDialog, - QTabWidget, QTabBar, QListWidget, QListWidgetItem, QDialog, +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, QStatusBar, QComboBox, QSplitter, QAbstractItemView, QDoubleSpinBox) + 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 ExcelParser, StepTableModel +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) # 发送执行器实例 + executionFinished = pyqtSignal(object) def __init__(self, procedureData, procedureId, dbManager): super().__init__() self.procedureData = procedureData - self.procedureId = procedureId # 新增规程ID - self.dbManager = dbManager # 新增数据库管理器 + 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.timer = QTimer() - self.timer.timeout.connect(self.autoExecuteStep) self.remainingCycles = 1 self.infiniteCycles = False - self.isFirstRun = True # 新增标志位,用于区分首次执行 - self.stepResults = [] # 新增:存储所有步骤执行结果的列表 + 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) - self.remainingTime = 0 # 当前轮次剩余时间(秒) - self.totalSteps = 0 # 当前轮次总步骤数 - - self.protocolManage = Globals.getValue("protocolManage") def initUi(self, testSteps): + """初始化用户界面""" layout = QVBoxLayout() - info_layout = QFormLayout() - info_layout.setLabelAlignment(Qt.AlignRight) - info_layout.addRow("规程名称:", QLabel(self.procedureData["规程信息"]["规程名称"])) - info_layout.addRow("规程编号:", QLabel(self.procedureData["规程信息"]["规程编号"])) - info_layout.addRow("规程类型:", QLabel(self.procedureData["规程信息"]["规程类型"])) - info_layout.addRow("测试用例:", QLabel(self.procedureData["测试用例信息"]["测试用例"])) - info_layout.addRow("用例编号:", QLabel(self.procedureData["测试用例信息"]["用例编号"])) - info_layout.addRow("工况描述:", QLabel(self.procedureData["测试用例信息"]["工况描述"])) + self.createInfoSection(layout) + self.createTableSection(layout, testSteps) + self.createControlSection(layout) + self.createSettingsSection(layout) - layout.addLayout(info_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) - # 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) + self.setupTableHeaders() layout.addWidget(QLabel("测试步骤:")) layout.addWidget(self.tableView) - # 创建控制按钮布局 - control_layout = QHBoxLayout() + 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) @@ -116,80 +145,89 @@ class StepExecutor(QWidget): self.exportButton.clicked.connect(self.onExportReportClicked) self.exportButton.setIcon(qta.icon('fa5s.file-alt', color='purple')) - control_layout.addWidget(self.autoButton) - control_layout.addWidget(self.stopButton) - control_layout.addWidget(self.nextButton) - control_layout.addWidget(self.resetButton) - control_layout.addWidget(self.exportButton) + buttons = [self.autoButton, self.stopButton, self.nextButton, + self.resetButton, self.exportButton] + for button in buttons: + controlLayout.addWidget(button) + + layout.addLayout(controlLayout) - # 添加循环设置 - cycle_layout = QHBoxLayout() - cycle_layout.addWidget(QLabel("执行轮次:")) + def createSettingsSection(self, layout): + """创建设置区域""" + cycleLayout = QHBoxLayout() + # 执行轮次设置 + cycleLayout.addWidget(QLabel("执行轮次:")) self.cycleSpin = QSpinBox() self.cycleSpin.setRange(1, 999) self.cycleSpin.setValue(1) - cycle_layout.addWidget(self.cycleSpin) + cycleLayout.addWidget(self.cycleSpin) self.infiniteCheckbox = QCheckBox("无限循环") - cycle_layout.addWidget(self.infiniteCheckbox) + cycleLayout.addWidget(self.infiniteCheckbox) - # 新增:状态显示标签 + # 状态显示 self.statusLabel = QLabel("就绪") self.statusLabel.setStyleSheet("color: blue; font-weight: bold;") - cycle_layout.addWidget(self.statusLabel) + cycleLayout.addWidget(self.statusLabel) - # 新增:倒计时显示标签 + # 倒计时显示 self.countdownLabel = QLabel("") self.countdownLabel.setStyleSheet("color: red; font-weight: bold; font-size: 14px;") - cycle_layout.addWidget(self.countdownLabel) + cycleLayout.addWidget(self.countdownLabel) - # 新增:步骤间隔时间设置 - cycle_layout.addWidget(QLabel("步骤间隔(秒):")) + # 步骤间隔设置 + 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) # 每次调整0.1秒 + self.stepIntervalSpin.setDecimals(2) + self.stepIntervalSpin.setSingleStep(0.1) self.stepIntervalSpin.setToolTip("设置步骤执行计时器的间隔时间(秒)") - cycle_layout.addWidget(self.stepIntervalSpin) - - cycle_layout.addStretch() - - # 将所有布局添加到主布局 - layout.addLayout(control_layout) - layout.addLayout(cycle_layout) + cycleLayout.addWidget(self.stepIntervalSpin) - # 设置主布局 - self.setLayout(layout) + cycleLayout.addStretch() + layout.addLayout(cycleLayout) - # 初始化工具栏 + def createToolbar(self): + """创建工具栏""" self.toolbar = QToolBar("执行工具栏") self.toolbar.setIconSize(QSize(24, 24)) - # 工具栏操作 - self.toolbar.addAction(qta.icon('fa5s.play', color='green'), "开始执行", self.startAutoExecute) - self.toolbar.addAction(qta.icon('fa5s.stop', color='red'), "停止执行", self.stopAutoExecute) - self.toolbar.addAction(qta.icon('fa5s.step-forward', color='blue'), "下一步", self.executeNextStep) - 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.onExportReportClicked) + 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 index.isValid(): - menu = QMenu() - jumpAction = menu.addAction("从该步骤开始执行") - if jumpAction: - jumpAction.triggered.connect(lambda: self.jumpExecute(index.row())) - detailAction = menu.addAction("查看步骤详情") - 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)) + 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']: @@ -197,36 +235,37 @@ class StepExecutor(QWidget): self.currentIndex = rowIndex + 1 def showStepDetail(self, row): - step_info = self.tableModel.getStepInfo(row) - if not step_info: + """显示步骤详情""" + stepInfo = self.tableModel.getStepInfo(row) + if not stepInfo: return - detail_dialog = QDialog(self) - detail_dialog.setWindowTitle("步骤详情") - detail_dialog.setMinimumWidth(500) + detailDialog = QDialog(self) + detailDialog.setWindowTitle("步骤详情") + detailDialog.setMinimumWidth(500) layout = QVBoxLayout() + formLayout = QFormLayout() - form_layout = QFormLayout() - form_layout.addRow("步骤ID", QLabel(step_info['stepId'])) - form_layout.addRow("步骤类型", QLabel("主步骤" if step_info['isMain'] else "子步骤")) - form_layout.addRow("步骤描述", QLabel(step_info['description'])) + formLayout.addRow("步骤ID", QLabel(stepInfo['stepId'])) + formLayout.addRow("步骤类型", QLabel("主步骤" if stepInfo['isMain'] else "子步骤")) + formLayout.addRow("步骤描述", QLabel(stepInfo['description'])) - if step_info['time']: - form_layout.addRow("执行时间", QLabel(step_info['time'].strftime("%Y-%m-%d %H:%M:%S"))) + if stepInfo['time']: + formLayout.addRow("执行时间", QLabel(stepInfo['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)) + if stepInfo['result'] is not None: + status = "成功" if stepInfo['result'] else "失败" + formLayout.addRow("执行结果", QLabel(status)) - layout.addLayout(form_layout) + layout.addLayout(formLayout) - button_box = QDialogButtonBox(QDialogButtonBox.Ok) - button_box.accepted.connect(detail_dialog.accept) - layout.addWidget(button_box) + buttonBox = QDialogButtonBox(QDialogButtonBox.Ok) + buttonBox.accepted.connect(detailDialog.accept) + layout.addWidget(buttonBox) - detail_dialog.setLayout(layout) - detail_dialog.exec_() + detailDialog.setLayout(layout) + detailDialog.exec_() def updateStatusDisplay(self, message, color="blue"): """更新状态显示""" @@ -234,277 +273,643 @@ class StepExecutor(QWidget): self.statusLabel.setStyleSheet(f"color: {color}; font-weight: bold;") def startAutoExecute(self): + """开始自动执行""" self.isRunning = True self.isActive = True - # 发送标签锁定信号 self.tabLockRequired.emit(True) - self.autoButton.setEnabled(False) - self.stopButton.setEnabled(True) - self.nextButton.setEnabled(False) - self.resetButton.setEnabled(False) - self.exportButton.setEnabled(False) - # 如果是首次执行,则初始化步骤结果集合 + self.updateButtonStates(False, True, False, False, False) + + # 每次开始新的执行时,都需要初始化状态 if self.isFirstRun: - self.stepResults = [] # 重置步骤结果集合 - self.tableModel.resetExecutionState() - self.isFirstRun = False # 标记已执行过 - # 创建第一个执行记录 - self.createNewExecutionRecord() + self.initializeFirstRun() + # 确保每次开始执行时都重新获取设置值 self.remainingCycles = self.cycleSpin.value() self.infiniteCycles = self.infiniteCheckbox.isChecked() - # 使用用户设置的步骤间隔时间启动计时器 - stepInterval = int(self.stepIntervalSpin.value() * 1000) # 转换为毫秒 - self.timer.start(stepInterval) + # 重置当前索引,确保从第一步开始执行 + self.currentIndex = 0 - # 更新状态显示 - self.updateStatusDisplay(f"开始执行 - 第1轮", "green") + 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.isRunning = False self.timer.stop() - self.autoButton.setEnabled(True) - self.stopButton.setEnabled(False) - self.nextButton.setEnabled(True) - self.resetButton.setEnabled(True) - self.exportButton.setEnabled(True) - # 注意: 这里不重置isActive,因为执行器仍处于激活状态 - # 执行结束时保存当前轮次的步骤结果到数据库 - 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.updateButtonStates(True, False, True, True, True) - # 更新状态显示 + self.saveCurrentResults() self.updateStatusDisplay("执行已停止", "orange") - - # 停止倒计时 self.stopCountdown() - # 发送执行完成信号 self.executionFinished.emit(self) - # 执行完毕后自动解锁标签页 self.tabLockRequired.emit(False) - # self.resetExecution() # 自动执行完毕后自动重置 + + 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(): - step_info = self.tableModel.getStepInfo(self.currentIndex) - if step_info and not step_info['isMain']: - self.executeStep(self.currentIndex) - self.currentIndex += 1 - - # 步骤执行完成后,更新倒计时 - self.startCountdown() + self.executeCurrentStep() 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 - self.timer.stop() - self.currentIndex = 0 - - should_continue = self.infiniteCycles or self.remainingCycles > 0 - if should_continue: - # 开始新一轮执行前,创建新的执行记录 - 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) + 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): """创建新的执行记录""" - execution_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.current_execution_id = self.dbManager.insertProcedureExecution( - self.procedureId, - execution_time + executionTime = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.currentExecutionId = self.dbManager.insertProcedureExecution( + self.procedureId, executionTime ) - print(f"创建新的执行记录,ID: {self.current_execution_id}") + print(f"创建新的执行记录,ID: {self.currentExecutionId}") 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}" + 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(): - step_info = self.tableModel.getStepInfo(self.currentIndex) - if step_info and not step_info['isMain']: + stepInfo = self.tableModel.getStepInfo(self.currentIndex) + if stepInfo and not stepInfo['isMain']: self.executeStep(self.currentIndex) self.currentIndex += 1 - # 手动执行不需要修改isRunning状态 def executeStep(self, row): - stepInfo = self.tableModel.getStepInfo(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 = False - execution_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - # 只保存基础类型,避免递归 - step_result = { + 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': execution_time, - 'result': bool(result) + 'execution_time': executionTime, + 'result': result } - self.stepResults.append(step_result) - if hasattr(self, 'current_execution_id'): - self.dbManager.updateStepResults(self.current_execution_id, self.stepResults) + + 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): # 修改参数名 + def handleStep(self, rowIndex, stepInfo): + """处理步骤""" description = stepInfo['description'] stepId = stepInfo['stepId'] print(f"处理步骤 {stepId}: {description}") - if "设置" in description: - return self.performWrite(stepId, description) # 修改方法名 - elif "检查" in description: - return self.performDataImport(stepId, description) # 修改方法名 - elif "接收" in description: - return self.performValidation(stepId, description) # 修改方法名 - elif "导出" in description: - return self.performExport(stepId, description) # 修改方法名 - elif "备份" in description: - return self.performBackup(stepId, description) # 修改方法名 - elif "发送" in description: - return self.performNotification(stepId, description) # 修改方法名 - - def simulateExecution(self, stepId, description): # 修改方法名 - import random - return random.random() < 0.9 - - def performWrite(self, stepId, description): # 修改方法名 - print(f"执行登录操作 (步骤 {stepId}): {description}") - return True - - def performDataImport(self, stepId, description): # 修改方法名 - import random - return random.random() < 0.95 - - def performValidation(self, stepId, description): # 修改方法名 - import random - return random.random() < 0.85 - - def performExport(self, stepId, description): # 修改方法名 - import random - return random.random() < 0.98 - - def performBackup(self, stepId, description): # 修改方法名 - return True - - def performNotification(self, stepId, description): # 修改方法名 - import random - return random.random() < 0.99 - - def performWait(self, stepId, description): # 修改方法名 - import time - waitTime = 1 # 修改变量名 - if "s" in description: - try: - waitTime = int(description.split("等待")[1].split("s")[0].strip()) - except: - pass - time.sleep(waitTime) - return True + # 从关键词字段库中提取关键词信息 + 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) - def performSetting(self, stepId, description): # 修改方法名 - return True + # 如果没有找到匹配的关键词,使用默认的模拟执行 + 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*([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 = [] # 重置步骤结果集合,禁止赋值为tableModel.stepData - if hasattr(self, 'current_execution_id'): - del self.current_execution_id + 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'')) + + 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'')) + + 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'')) + + 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'')) + 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'')) + 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.remainingTime = int(remainingSteps * stepInterval) + self.countdownTimer.start(1000) self.updateCountdown() else: - # 如果没有剩余步骤,清空显示 self.countdownLabel.setText("") def stopCountdown(self): @@ -513,17 +918,16 @@ class StepExecutor(QWidget): self.countdownLabel.setText("") def updateCountdown(self): - """更新倒计时显示""" + """更新倒计时""" if self.remainingTime > 0: minutes = self.remainingTime // 60 seconds = self.remainingTime % 60 if minutes > 0: - countdown_text = f"当前轮次剩余: {minutes:02d}:{seconds:02d}" + countdownText = f"当前轮次剩余: {minutes:02d}:{seconds:02d}" else: - countdown_text = f"当前轮次剩余: {seconds}秒" + countdownText = f"当前轮次剩余: {seconds}秒" - # 根据剩余时间调整颜色 if self.remainingTime <= 10: self.countdownLabel.setStyleSheet("color: red; font-weight: bold; font-size: 14px;") elif self.remainingTime <= 30: @@ -531,17 +935,13 @@ class StepExecutor(QWidget): else: self.countdownLabel.setStyleSheet("color: blue; font-weight: bold; font-size: 14px;") - self.countdownLabel.setText(countdown_text) + 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 updateCycleCountdown(self): - """更新轮次剩余时间倒计时 - 已废弃,保留兼容性""" - self.updateCountdown() - def resetCountdown(self): """重置倒计时""" self.stopCountdown() @@ -549,19 +949,18 @@ class StepExecutor(QWidget): 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 + 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 \ No newline at end of file diff --git a/model/ProcedureModel/ProcedureProcessor.py b/model/ProcedureModel/ProcedureProcessor.py index fdc3d86..5fe4127 100644 --- a/model/ProcedureModel/ProcedureProcessor.py +++ b/model/ProcedureModel/ProcedureProcessor.py @@ -133,9 +133,17 @@ class StepTableModel(QAbstractTableModel): elif col == 2: return step['time'].strftime("%Y-%m-%d %H:%M:%S") if step['time'] else '' elif col == 3: - return {True: '是', False: '否', None: ''}[step['result']] + # print(step['result']) + if not step['result']: + return ' ' + if '失败' in step['result']: + return '✘' + if step['result'] == '未检测到关键字': + return '✘' + return '✓' elif col == 4: - return {True: '成功', False: '失败', None: ''}[step['result']] + # print(step['result']) + return step['result'] elif col == 5: return step['note'] if step['note'] else '' @@ -181,6 +189,7 @@ class StepTableModel(QAbstractTableModel): def updateStepResult(self, row, result, time): if 0 <= row < len(self.stepData): + # print(result) self.stepData[row]['executed'] = True self.stepData[row]['time'] = time self.stepData[row]['result'] = result diff --git a/utils/DBModels/ProcedureModel.py b/utils/DBModels/ProcedureModel.py index d495a6c..f61b0e6 100644 --- a/utils/DBModels/ProcedureModel.py +++ b/utils/DBModels/ProcedureModel.py @@ -57,6 +57,18 @@ class DatabaseManager: ) """) + # 创建关键词字段库表 + self.cursor.execute(""" + CREATE TABLE IF NOT EXISTS step_keywords ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + keyword TEXT NOT NULL, + operation_type TEXT NOT NULL, + description TEXT, + created_at TEXT DEFAULT CURRENT_TIMESTAMP, + UNIQUE(keyword, operation_type) + ) + """) + # 插入默认分类 self.cursor.execute(""" INSERT OR IGNORE INTO categories (name) VALUES @@ -66,6 +78,19 @@ class DatabaseManager: ('维护规程') """) + # 插入默认关键词 + self.cursor.execute(""" + INSERT OR IGNORE INTO step_keywords (keyword, operation_type, description) VALUES + ('设置', 'set', '设置操作类型'), + ('检查', 'check', '检查操作类型'), + ('等待', 'wait', '等待操作类型'), + ('接收', 'deltaT', '接收操作类型'), + ('配置', 'set', '配置操作类型'), + ('验证', 'check', '验证操作类型'), + ('暂停', 'wait', '暂停操作类型'), + ('获取', 'deltaT', '获取操作类型') + """) + # 确保procedures表有report_path列 try: self.cursor.execute("ALTER TABLE procedures ADD COLUMN report_path TEXT DEFAULT ''") @@ -299,4 +324,72 @@ class DatabaseManager: return self.cursor.lastrowid def close(self): - self.conn.close() \ No newline at end of file + self.conn.close() + + # 关键词字段库相关方法 + def getStepKeywords(self, operationType=None): + """获取关键词列表""" + if operationType: + self.cursor.execute(""" + SELECT id, keyword, operation_type, description, created_at + FROM step_keywords + WHERE operation_type = ? + ORDER BY keyword + """, (operationType,)) + else: + self.cursor.execute(""" + SELECT id, keyword, operation_type, description, created_at + FROM step_keywords + ORDER BY keyword + """) + return self.cursor.fetchall() + + def addStepKeyword(self, keyword, operationType, description=""): + """添加关键词""" + try: + self.cursor.execute(""" + INSERT INTO step_keywords (keyword, operation_type, description) + VALUES (?, ?, ?) + """, (keyword, operationType, description)) + self.conn.commit() + return self.cursor.lastrowid + except sqlite3.IntegrityError: + return None + + def updateStepKeyword(self, keywordId, newKeyword, operationType, description=""): + """更新关键词""" + try: + self.cursor.execute(""" + UPDATE step_keywords + SET keyword = ?, operation_type = ?, description = ? + WHERE id = ? + """, (newKeyword, operationType, description, keywordId)) + self.conn.commit() + return self.cursor.rowcount > 0 + except sqlite3.IntegrityError: + return False + + def deleteStepKeyword(self, keywordId): + """删除关键词""" + self.cursor.execute("DELETE FROM step_keywords WHERE id = ?", (keywordId,)) + self.conn.commit() + return self.cursor.rowcount > 0 + + def getKeywordByText(self, text): + """根据文本查找匹配的关键词""" + self.cursor.execute(""" + SELECT keyword, operation_type + FROM step_keywords + WHERE ? LIKE '%' || keyword || '%' + ORDER BY LENGTH(keyword) DESC + """, (text,)) + return self.cursor.fetchall() + + def getOperationTypes(self): + """获取所有操作类型""" + self.cursor.execute(""" + SELECT DISTINCT operation_type + FROM step_keywords + ORDER BY operation_type + """) + return [row[0] for row in self.cursor.fetchall()] \ No newline at end of file