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.

716 lines
29 KiB
Python

4 months ago
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
import qtawesome as qta
from datetime import datetime
# 导入其他模块
from model.ProcedureModel.ProcedureProcessor import ExcelParser, StepTableModel
from utils.DBModels.ProcedureModel import DatabaseManager
4 months ago
from UI.ProcedureManager.HistoryViewer import HistoryViewerWidget
from UI.ProcedureManager.StepExecutor import StepExecutor # 修改导入路径
4 months ago
class ExecutionDetailDialog(QDialog):
"""步骤详情对话框"""
def __init__(self, step_info, parent=None):
super().__init__(parent)
self.setWindowTitle("步骤详情")
self.setMinimumWidth(600)
layout = QVBoxLayout()
form_layout = QFormLayout()
form_layout.addRow("步骤ID", QLabel(step_info['step_id']))
form_layout.addRow("步骤描述", QLabel(step_info['step_description']))
form_layout.addRow("执行时间", QLabel(step_info['execution_time']))
form_layout.addRow("执行结果", QLabel("成功" if step_info['result'] else "失败"))
layout.addLayout(form_layout)
button_box = QDialogButtonBox(QDialogButtonBox.Ok)
button_box.accepted.connect(self.accept)
layout.addWidget(button_box)
self.setLayout(layout)
class AddCategoryDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("添加新分类")
self.setFixedSize(300, 150)
layout = QVBoxLayout()
form_layout = QFormLayout()
self.nameEdit = QLineEdit()
self.nameEdit.setPlaceholderText("输入分类名称")
form_layout.addRow("分类名称:", self.nameEdit)
layout.addLayout(form_layout)
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)
self.setLayout(layout)
def getCategoryName(self):
return self.nameEdit.text().strip()
class ProcedureManager(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("规程管理系统")
self.setGeometry(100, 100, 1200, 800)
# 创建工具栏
self.toolbar = QToolBar("主工具栏")
self.toolbar.setIconSize(QSize(32, 32))
# 新增:在工具栏级别统一设置文字显示位置
self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
self.addToolBar(self.toolbar)
self.createActions()
# 添加状态栏
self.statusBar = QStatusBar()
self.setStatusBar(self.statusBar)
4 months ago
# 批量执行相关变量
self.batchExecutionQueue = [] # 批量执行队列
self.currentBatchIndex = 0 # 当前执行的规程索引
self.isBatchExecuting = False # 是否正在批量执行
self.batchExecutionTimer = QTimer() # 批量执行检查定时器
self.batchExecutionTimer.timeout.connect(self.checkBatchExecutionStatus)
self.currentBatchTabIndex = -1 # 当前批量执行的标签页索引
4 months ago
def initUI(self):
# 加载样式表
self.loadStylesheet()
# 创建主控件 - 已经是QTabWidget
self.tabs = QTabWidget()
self.setCentralWidget(self.tabs)
# 启用标签页关闭按钮
self.tabs.setTabsClosable(True)
self.tabs.tabCloseRequested.connect(self.closeTab) # 新增关闭标签页信号连接
# 添加初始的规程管理标签页必须先创建UI组件
self.initProcedureManagementTab() # 先创建UI组件
# 初始化其他组件在UI创建后调用
self.loadCategories() # 现在categoryList已存在
# 新增:确保加载第一个分类的规程
if self.categoryList.count() > 0:
self.categorySelected(self.categoryList.item(0), None)
# 初始化标签锁定状态
self.activeExecutorIndex = -1
def initDB(self):
# 初始化数据库
self.db = DatabaseManager()
# 新增:关闭标签页的处理方法
def closeTab(self, index):
# 规程管理标签页(索引0)不能关闭
if index == 0:
return
widget = self.tabs.widget(index)
# 检查是否有正在运行的执行器
if isinstance(widget, StepExecutor) and widget.isRunning:
QMessageBox.warning(
self,
"操作被阻止",
"当前有规程正在执行中,请先停止执行后再关闭标签页。"
)
return
# 如果是当前激活的执行器标签页,解锁所有标签页
if index == self.activeExecutorIndex:
self.lockTabs(False, index)
# 移除标签页
self.tabs.removeTab(index)
widget.deleteLater()
# 修改:锁定/解锁标签页方法(增加关闭按钮控制)
def lockTabs(self, lock, currentIndex):
4 months ago
"""锁定或解锁标签页只用Qt原生关闭按钮方案"""
4 months ago
self.activeExecutorIndex = currentIndex if lock else -1
4 months ago
self.tabs.setTabsClosable(True)
4 months ago
for index in range(self.tabs.count()):
enable = not lock or index == currentIndex
self.tabs.setTabEnabled(index, enable)
if index == 0:
self.tabs.setTabEnabled(0, True)
4 months ago
if index > 0:
if lock and index != currentIndex:
self.tabs.tabBar().setTabButton(index, QTabBar.RightSide, None)
# 解锁时强制恢复所有关闭按钮
if not lock:
self.tabs.setTabsClosable(False)
self.tabs.setTabsClosable(True)
4 months ago
# 修改:打开规程执行界面(显示规程全名)
def openProcedureInExecutor(self, item=None):
# 如果通过工具栏调用item为None使用当前选中项
currentItem = item or self.procedureList.currentItem()
if not currentItem:
return
# 检查是否有正在运行的执行器
if self.hasRunningExecutor():
QMessageBox.warning(
self,
"操作被阻止",
"当前有规程正在执行中,请先停止执行后再打开其他规程。"
)
return
procId = currentItem.data(Qt.UserRole) # 修改变量名
4 months ago
# print(procId, 22222222222222)
4 months ago
procedureData = self.db.getProcedureContent(procId) # 修改变量名
if not procedureData:
QMessageBox.warning(self, "打开失败", "无法获取规程内容")
return
# 创建新的执行界面传入规程ID和数据库管理器
executor = StepExecutor(procedureData, procId, self.db)
# 获取规程名称和编号
procName = procedureData["规程信息"]["规程名称"]
procNumber = procedureData["规程信息"]["规程编号"]
# 添加为新标签页,显示规程全名(名称+编号)
tab_index = self.tabs.addTab(executor, f"{procName} ({procNumber})")
# 记录当前执行器的标签索引
executor.tabLockRequired.connect(lambda lock: self.lockTabs(lock, tab_index))
# 切换到新添加的标签页
self.tabs.setCurrentWidget(executor)
4 months ago
executor.executionFinished.connect(lambda _: QTimer.singleShot(0, lambda: executor.resetExecution(fromAuto=True)))
4 months ago
def initProcedureManagementTab(self):
"""创建规程管理主标签页"""
mainWidget = QWidget()
mainLayout = QHBoxLayout()
self.categoryList = QListWidget()
self.categoryList.setFixedWidth(200)
self.categoryList.currentItemChanged.connect(self.categorySelected)
# 设置分类列表接受拖放
self.categoryList.setAcceptDrops(True)
self.categoryList.setDragDropMode(QAbstractItemView.DropOnly)
# 新增:设置分类列表右键菜单
self.categoryList.setContextMenuPolicy(Qt.CustomContextMenu)
self.categoryList.customContextMenuRequested.connect(self.showCategoryContextMenu) # 新增
self.procedureList = QListWidget()
# 设置规程列表支持拖拽
self.procedureList.setDragEnabled(True)
self.procedureList.setDragDropMode(QAbstractItemView.DragOnly)
self.procedureList.itemDoubleClicked.connect(
lambda item: self.openProcedureInExecutor(item)
)
# 新增:设置规程列表右键菜单
self.procedureList.setContextMenuPolicy(Qt.CustomContextMenu)
self.procedureList.customContextMenuRequested.connect(self.showProcedureContextMenu) # 新增
# 新增:拖拽事件处理
self.procedureList.dragEnterEvent = self.procedureDragEnterEvent
self.categoryList.dropEvent = self.categoryDropEvent
leftPanel = QWidget()
leftLayout = QVBoxLayout()
leftLayout.addWidget(QLabel("规程分类"))
leftLayout.addWidget(self.categoryList)
leftPanel.setLayout(leftLayout)
leftPanel.setFixedWidth(220)
rightPanel = QWidget()
rightLayout = QVBoxLayout()
rightLayout.addWidget(QLabel("规程列表"))
rightLayout.addWidget(self.procedureList)
rightPanel.setLayout(rightLayout)
mainLayout.addWidget(leftPanel)
mainLayout.addWidget(rightPanel)
mainWidget.setLayout(mainLayout)
self.tabs.addTab(mainWidget, "规程管理")
def createActions(self):
self.importAction = QAction(
qta.icon('fa5s.file-import', color='green'),
"导入规程",
self
)
self.importAction.setIconText("导入规程")
self.importAction.setShortcut("Ctrl+I")
self.importAction.setStatusTip("导入Excel规程文件")
self.importAction.triggered.connect(self.importProcedure)
self.toolbar.addAction(self.importAction) # 修改变量名
self.addCategoryAction = QAction(
qta.icon('fa5s.folder-plus', color='blue'),
"添加分类",
self
)
self.addCategoryAction.setIconText("添加分类")
self.addCategoryAction.setStatusTip("添加新的分类")
self.addCategoryAction.triggered.connect(self.addCategory)
self.toolbar.addAction(self.addCategoryAction) # 修改变量名
self.deleteCategoryAction = QAction(
qta.icon('fa5s.folder-minus', color='red'),
"删除分类",
self
)
self.deleteCategoryAction.setIconText("删除分类")
self.deleteCategoryAction.setStatusTip("删除当前分类")
self.deleteCategoryAction.triggered.connect(self.deleteCurrentCategory)
self.toolbar.addAction(self.deleteCategoryAction) # 修改变量名
self.deleteProcedureAction = QAction(
qta.icon('fa5s.trash', color='red'),
"删除规程",
self
)
self.deleteProcedureAction.setIconText("删除规程")
self.deleteProcedureAction.setStatusTip("删除选中的规程")
self.deleteProcedureAction.triggered.connect(self.deleteSelectedProcedure)
self.toolbar.addAction(self.deleteProcedureAction) # 修改变量名
self.openProcedureAction = QAction(
qta.icon('fa5s.folder-open', color='orange'),
"打开规程",
self
)
self.openProcedureAction.setIconText("打开规程")
self.openProcedureAction.setStatusTip("在步骤执行工具中打开选中的规程")
self.openProcedureAction.triggered.connect(self.openProcedureInExecutor)
self.toolbar.addAction(self.openProcedureAction) # 修改变量名
# 添加历史记录查看器动作
self.historyAction = QAction(
qta.icon('fa5s.history', color='purple'),
"历史记录",
self
)
self.historyAction.setIconText("历史记录")
self.historyAction.setStatusTip("查看历史执行记录")
self.historyAction.triggered.connect(self.openHistoryViewer)
self.toolbar.addAction(self.historyAction)
4 months ago
# 添加批量执行规程动作
self.batchExecuteAction = QAction(
qta.icon('fa5s.play-circle', color='green'),
"批量执行",
self
)
self.batchExecuteAction.setIconText("批量执行")
self.batchExecuteAction.setStatusTip("按顺序批量执行当前分类中的所有规程")
self.batchExecuteAction.triggered.connect(self.batchExecuteProcedures)
self.toolbar.addAction(self.batchExecuteAction)
4 months ago
def loadCategories(self):
self.categoryList.clear()
categories = self.db.getCategories()
for catId, catName in categories:
item = QListWidgetItem(catName)
item.setData(Qt.UserRole, catId)
self.categoryList.addItem(item)
if self.categoryList.count() > 0:
self.categoryList.setCurrentRow(0)
# 新增:手动触发分类选择信号
self.categoryList.itemSelectionChanged.emit()
def loadProcedures(self, categoryId=None):
self.procedureList.clear()
procedures = self.db.getProcedures(categoryId)
for procId, name, number, type, createdAt in procedures:
item = QListWidgetItem(f"{name} ({number})")
item.setData(Qt.UserRole, procId)
item.setToolTip(f"类型: {type}\n创建时间: {createdAt}")
self.procedureList.addItem(item)
def categorySelected(self, currentItem, previousItem):
if currentItem:
categoryId = currentItem.data(Qt.UserRole)
self.loadProcedures(categoryId)
def importProcedure(self):
filePath, _ = QFileDialog.getOpenFileName(
self,
"选择规程文件",
"",
"Excel文件 (*.xlsx *.xls)"
)
if not filePath:
return
try:
parsedData = ExcelParser.parseProcedure(filePath)
currentItem = self.categoryList.currentItem()
categoryId = currentItem.data(Qt.UserRole) if currentItem else None
procInfo = parsedData["规程信息"]
# 添加空字符串作为report_path参数
procId = self.db.addProcedure(
categoryId,
procInfo["规程名称"],
procInfo["规程编号"],
procInfo["规程类型"],
parsedData,
"" # 显式传递空字符串作为report_path
)
if procId:
self.statusBar.showMessage(f"成功导入规程: {procInfo['规程名称']}", 5000)
self.loadProcedures(categoryId)
else:
QMessageBox.warning(self, "导入失败", "无法导入规程,请检查数据库连接")
except Exception as e:
QMessageBox.critical(self, "导入错误", f"导入规程时发生错误:\n{str(e)}")
def addCategory(self):
dialog = AddCategoryDialog(self)
if dialog.exec_() == QDialog.Accepted:
categoryName = dialog.getCategoryName()
if categoryName:
if self.db.addCategory(categoryName):
self.loadCategories()
else:
QMessageBox.warning(self, "添加失败", "分类名称已存在,请使用其他名称")
def deleteCurrentCategory(self):
currentItem = self.categoryList.currentItem()
if not currentItem:
return
categoryId = currentItem.data(Qt.UserRole)
categoryName = currentItem.text()
# 检查是否为默认分类
if categoryName == "默认分类":
QMessageBox.warning(
self,
"操作禁止",
"默认分类不可删除!"
)
return
reply = QMessageBox.question(
self,
"确认删除",
f"确定要删除分类 '{categoryName}' 吗?\n该分类下的规程将移动到默认分类。",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
if self.db.deleteCategory(categoryId):
self.loadCategories()
self.statusBar.showMessage(f"已删除分类: {categoryName}", 5000)
def deleteSelectedProcedure(self):
currentItem = self.procedureList.currentItem()
if not currentItem:
return
procId = currentItem.data(Qt.UserRole)
procName = currentItem.text()
reply = QMessageBox.question(
self,
"确认删除",
f"确定要删除规程 '{procName}' 吗?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
if self.db.deleteProcedure(procId):
currentCategoryItem = self.categoryList.currentItem()
categoryId = currentCategoryItem.data(Qt.UserRole) if currentCategoryItem else None
self.loadProcedures(categoryId)
self.statusBar.showMessage(f"已删除规程: {procName}", 5000)
def hasRunningExecutor(self):
"""检查当前是否有正在运行的执行器 - 修正实现"""
for index in range(self.tabs.count()):
widget = self.tabs.widget(index)
# 修正: 只检查isRunning状态移除对isActive的检查
if isinstance(widget, StepExecutor) and widget.isRunning:
return True
return False
# 新增:打开历史记录查看器
def openHistoryViewer(self):
4 months ago
historyViewer = HistoryViewerWidget(self.db, self)
4 months ago
historyViewer.exec_()
def loadStylesheet(self):
qssPath = "Static/Procedure.qss"
try:
qssFile = QFile(qssPath) # 修改变量名
if qssFile.exists():
qssFile.open(QFile.ReadOnly | QFile.Text)
stream = QTextStream(qssFile)
stream.setCodec("UTF-8")
self.setStyleSheet(stream.readAll())
qssFile.close()
print(f"成功加载样式表: {qssPath}")
else:
print(f"警告:样式表文件不存在: {qssPath}")
except Exception as e:
print(f"加载样式表失败: {str(e)}")
def closeEvent(self, event):
self.db.close()
event.accept()
def procedureDragEnterEvent(self, event):
"""处理规程列表的拖拽进入事件"""
if event.mimeData().hasText():
event.acceptProposedAction()
def categoryDropEvent(self, event):
"""处理分类列表的拖放事件:将拖拽过来的规程移动到当前分类"""
# 获取拖拽源
source = event.source()
# 确保拖拽源是规程列表
if source != self.procedureList:
event.ignore()
return
# 获取选中的规程项(拖拽项)
items = self.procedureList.selectedItems()
if not items:
event.ignore()
return
# 获取目标分类项(鼠标释放位置对应的分类项)
pos = event.pos()
targetItem = self.categoryList.itemAt(pos)
if not targetItem:
event.ignore()
return
# 获取目标分类ID
targetCategoryId = targetItem.data(Qt.UserRole)
# 获取被拖拽的规程ID只处理第一个选中的项
procItem = items[0]
procId = procItem.data(Qt.UserRole)
procName = procItem.text()
# 更新数据库
if self.db.updateProcedureCategory(procId, targetCategoryId):
# 更新成功,重新加载目标分类的规程列表(因为规程已经移动到新分类)
self.loadProcedures(targetCategoryId)
# 新增:设置当前选中的分类为目标分类
self.categoryList.setCurrentItem(targetItem)
# 显示状态信息
self.statusBar.showMessage(f"已将规程 '{procName}' 移动到分类 '{targetItem.text()}'", 5000)
else:
QMessageBox.warning(self, "移动失败", "移动规程失败,请检查数据库连接")
event.accept()
# 新增:分类列表右键菜单
def showCategoryContextMenu(self, pos):
"""显示分类列表的右键菜单"""
item = self.categoryList.itemAt(pos)
if item:
menu = QMenu()
deleteAction = menu.addAction("删除分类")
deleteAction.triggered.connect(self.deleteCurrentCategory)
menu.exec_(self.categoryList.mapToGlobal(pos))
# 新增:规程列表右键菜单
def showProcedureContextMenu(self, pos):
"""显示规程列表的右键菜单"""
item = self.procedureList.itemAt(pos)
if item:
menu = QMenu()
deleteAction = menu.addAction("删除规程")
deleteAction.triggered.connect(self.deleteSelectedProcedure)
menu.exec_(self.procedureList.mapToGlobal(pos))
4 months ago
# 批量执行相关方法
def batchExecuteProcedures(self):
"""开始批量执行当前分类中的所有规程"""
# 检查是否有正在运行的执行器
if self.hasRunningExecutor():
QMessageBox.warning(
self,
"操作被阻止",
"当前有规程正在执行中,请先停止执行后再开始批量执行。"
)
return
# 获取当前分类的所有规程
currentItem = self.categoryList.currentItem()
if not currentItem:
QMessageBox.warning(self, "未选择分类", "请先选择一个分类")
return
categoryId = currentItem.data(Qt.UserRole)
procedures = self.db.getProcedures(categoryId)
if not procedures:
QMessageBox.information(self, "无规程", "当前分类中没有规程")
return
# 确认批量执行
reply = QMessageBox.question(
self,
"确认批量执行",
f"确定要批量执行当前分类中的 {len(procedures)} 个规程吗?\n"
f"规程将按顺序逐个执行,每个规程执行完毕后自动开始下一个。",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
# 初始化批量执行队列
self.batchExecutionQueue = [(procId, name, number) for procId, name, number, type, createdAt in procedures]
self.currentBatchIndex = 0
self.isBatchExecuting = True
# 更新状态
self.statusBar.showMessage(f"开始批量执行 {len(procedures)} 个规程", 3000)
# 开始执行第一个规程
self.executeNextBatchProcedure()
def executeNextBatchProcedure(self):
"""执行下一个规程"""
print(f"executeNextBatchProcedure: 批量执行={self.isBatchExecuting}, 当前索引={self.currentBatchIndex}, 队列长度={len(self.batchExecutionQueue)}")
if not self.isBatchExecuting or self.currentBatchIndex >= len(self.batchExecutionQueue):
print("批量执行结束条件满足调用finishBatchExecution")
self.finishBatchExecution()
return
# 获取当前要执行的规程
procId, name, number = self.batchExecutionQueue[self.currentBatchIndex]
print(f"准备执行规程: {name} ({number}) - {self.currentBatchIndex + 1}/{len(self.batchExecutionQueue)}")
# 获取规程数据
procedureData = self.db.getProcedureContent(procId)
if not procedureData:
QMessageBox.warning(self, "获取规程失败", f"无法获取规程 {name} 的内容")
self.currentBatchIndex += 1
self.executeNextBatchProcedure()
return
# 创建执行器
executor = StepExecutor(procedureData, procId, self.db)
# 设置规程全名
procName = procedureData["规程信息"]["规程名称"]
procNumber = procedureData["规程信息"]["规程编号"]
tab_title = f"批量执行-{procName}({procNumber})"
# 添加为新标签页
tab_index = self.tabs.addTab(executor, tab_title)
self.currentBatchTabIndex = tab_index # 记录当前标签页索引
print(f"创建标签页: {tab_title}, 索引={tab_index}")
# 连接标签锁定信号
executor.tabLockRequired.connect(lambda lock: self.lockTabs(lock, tab_index))
# 切换到新添加的标签页
self.tabs.setCurrentWidget(executor)
# 开始自动执行
executor.startAutoExecute()
print(f"开始自动执行规程: {procName}")
# 启动批量执行状态检查定时器(如果还没启动)
if not self.batchExecutionTimer.isActive():
self.batchExecutionTimer.start(1000) # 每秒检查一次
print("启动批量执行状态检查定时器")
else:
print("批量执行状态检查定时器已在运行")
# 更新状态
self.statusBar.showMessage(f"正在执行: {name} ({number}) - {self.currentBatchIndex + 1}/{len(self.batchExecutionQueue)}", 0)
def checkBatchExecutionStatus(self):
"""检查批量执行状态"""
if not self.isBatchExecuting:
return
# 检查当前是否有正在运行的执行器
has_running = self.hasRunningExecutor()
print(f"批量执行状态检查: 当前索引={self.currentBatchIndex}, 队列长度={len(self.batchExecutionQueue)}, 有运行执行器={has_running}")
if not has_running:
# 如果没有正在运行的执行器,说明当前规程执行完成
print(f"规程执行完成,准备执行下一个规程")
# 确保当前标签页存在且有效
if self.currentBatchTabIndex > 0 and self.currentBatchTabIndex < self.tabs.count():
# 关闭当前执行器标签页
self.tabs.removeTab(self.currentBatchTabIndex)
self.currentBatchTabIndex = -1 # 重置标签页索引
print(f"已关闭标签页,索引={self.currentBatchTabIndex}")
# 移动到下一个规程
self.currentBatchIndex += 1
print(f"移动到下一个规程,新索引={self.currentBatchIndex}")
if self.currentBatchIndex >= len(self.batchExecutionQueue):
# 所有规程执行完成
print("所有规程执行完成")
self.finishBatchExecution()
else:
# 延迟一下再执行下一个规程,让用户看到执行结果
print(f"延迟2秒后执行下一个规程: {self.currentBatchIndex + 1}/{len(self.batchExecutionQueue)}")
# 停止当前定时器,避免重复检查
self.batchExecutionTimer.stop()
QTimer.singleShot(2000, self.executeNextBatchProcedure)
def finishBatchExecution(self):
"""完成批量执行"""
self.isBatchExecuting = False
self.batchExecutionQueue = []
self.currentBatchIndex = 0
self.currentBatchTabIndex = -1 # 重置标签页索引
self.batchExecutionTimer.stop()
# 更新状态
self.statusBar.showMessage("批量执行完成", 5000)
# 显示完成消息
QMessageBox.information(
self,
"批量执行完成",
"所有规程已按顺序执行完毕!"
)