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