You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

347 lines
14 KiB
Python

import sys
import os
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 utils.DBModels.ProcedureModel import DatabaseManager
from UI.ProcedureManager.StepExecutor import StepExecutor
class HistoryViewerWidget(QWidget):
def __init__(self, dbManager, parent=None):
super().__init__(parent)
self.dbManager = dbManager
self.initUi()
self.loadHistory()
def initUi(self):
layout = QVBoxLayout()
# 搜索栏
searchLayout = QHBoxLayout()
self.searchEdit = QLineEdit()
self.searchEdit.setPlaceholderText("搜索规程...")
self.searchEdit.textChanged.connect(self.loadHistory)
searchLayout.addWidget(QLabel("搜索:"))
searchLayout.addWidget(self.searchEdit)
layout.addLayout(searchLayout)
# 历史记录表格
self.table = QTableView()
self.table.setEditTriggers(QTableView.NoEditTriggers)
self.model = QStandardItemModel()
self.model.setHorizontalHeaderLabels(["ID", "规程全名", "类型", "执行时间", "报告路径"])
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.setupTableDisplay(self.table)
# 步骤详情表格
self.stepTable = QTableView()
self.stepTable.setEditTriggers(QTableView.NoEditTriggers)
self.stepModel = QStandardItemModel()
self.stepModel.setHorizontalHeaderLabels(["步骤ID", "步骤描述", "执行时间", "执行结果"])
self.stepTable.setModel(self.stepModel)
self.setupTableDisplay(self.stepTable)
# 分割窗口
splitter = QSplitter(Qt.Vertical)
splitter.addWidget(self.table)
splitter.addWidget(self.stepTable)
splitter.setSizes([400, 400])
layout.addWidget(splitter)
# 操作按钮
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)
buttonLayout.addWidget(self.deleteButton)
self.exportButton = QPushButton("导出报告")
self.exportButton.setIcon(qta.icon('fa5s.file-export', color='green'))
self.exportButton.clicked.connect(self.exportReport)
buttonLayout.addWidget(self.exportButton)
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:
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):
"""打开报告或详情"""
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,
operationDetail,
datetime.now().strftime("%Y-%m-%d %H:%M:%S")
)
def showStepDetails(self, stepResults):
"""显示步骤详情"""
self.stepModel.removeRows(0, self.stepModel.rowCount())
if not stepResults:
return
for step in stepResults:
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):
"""删除选中的历史记录"""
selectedIndexes = self.table.selectionModel().selectedRows()
if not selectedIndexes:
QMessageBox.warning(self, "未选择", "请先选择要删除的历史记录")
return
executionIds = []
for index in selectedIndexes:
executionId = self.model.item(index.row(), 0).text()
executionIds.append(executionId)
reply = QMessageBox.question(
self,
"确认删除",
f"确定要删除选中的 {len(executionIds)} 条历史记录吗?\n此操作不可恢复!",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
success = self.dbManager.deleteExecutionHistory(executionIds)
if success:
QMessageBox.information(self, "删除成功", "已成功删除选中的历史记录")
self.loadHistory()
else:
QMessageBox.warning(self, "删除失败", "删除历史记录时发生错误")
def exportReport(self):
"""导出报告"""
selectedIndexes = self.table.selectionModel().selectedRows()
if not selectedIndexes:
QMessageBox.warning(self, "未选择", "请先选择要导出的历史记录")
return
if len(selectedIndexes) > 1:
QMessageBox.warning(self, "选择过多", "一次只能导出一个历史记录的报告")
return
index = selectedIndexes[0]
executionId = self.model.item(index.row(), 0).text()
executionData = self.dbManager.getExecutionDetails(executionId)
if not executionData:
QMessageBox.warning(self, "数据错误", "无法获取执行详情数据")
return
try:
executor = StepExecutor(executionData['procedure_content'],
executionData['procedure_id'],
self.dbManager)
executor.stepResults = executionData['step_results']
for step in executor.tableModel.stepData:
stepResult = next((s for s in executionData['step_results']
if s['step_id'] == step['stepId']), None)
if stepResult:
step['executed'] = True
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():
deleteAction = menu.addAction(qta.icon('fa5s.trash', color='red'), "删除历史记录")
deleteAction.triggered.connect(self.deleteSelectedHistory)
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()