|
|
|
|
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
|
|
|
|
|
from UI.ProcedureManager.HistoryViewer import HistoryViewerWidget
|
|
|
|
|
from UI.ProcedureManager.StepExecutor import StepExecutor # 修改导入路径
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
# 批量执行相关变量
|
|
|
|
|
self.batchExecutionQueue = [] # 批量执行队列
|
|
|
|
|
self.currentBatchIndex = 0 # 当前执行的规程索引
|
|
|
|
|
self.isBatchExecuting = False # 是否正在批量执行
|
|
|
|
|
self.batchExecutionTimer = QTimer() # 批量执行检查定时器
|
|
|
|
|
self.batchExecutionTimer.timeout.connect(self.checkBatchExecutionStatus)
|
|
|
|
|
self.currentBatchTabIndex = -1 # 当前批量执行的标签页索引
|
|
|
|
|
|
|
|
|
|
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):
|
|
|
|
|
"""锁定或解锁标签页(只用Qt原生关闭按钮方案)"""
|
|
|
|
|
self.activeExecutorIndex = currentIndex if lock else -1
|
|
|
|
|
self.tabs.setTabsClosable(True)
|
|
|
|
|
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)
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
# 修改:打开规程执行界面(显示规程全名)
|
|
|
|
|
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) # 修改变量名
|
|
|
|
|
# print(procId, 22222222222222)
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
executor.executionFinished.connect(lambda _: QTimer.singleShot(0, lambda: executor.resetExecution(fromAuto=True)))
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
# 添加批量执行规程动作
|
|
|
|
|
self.batchExecuteAction = QAction(
|
|
|
|
|
qta.icon('fa5s.play-circle', color='green'),
|
|
|
|
|
"批量执行",
|
|
|
|
|
self
|
|
|
|
|
)
|
|
|
|
|
self.batchExecuteAction.setIconText("批量执行")
|
|
|
|
|
self.batchExecuteAction.setStatusTip("按顺序批量执行当前分类中的所有规程")
|
|
|
|
|
self.batchExecuteAction.triggered.connect(self.batchExecuteProcedures)
|
|
|
|
|
self.toolbar.addAction(self.batchExecuteAction)
|
|
|
|
|
|
|
|
|
|
def loadCategories(self):
|
|
|
|
|
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):
|
|
|
|
|
historyViewer = HistoryViewerWidget(self.db, self)
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
# 批量执行相关方法
|
|
|
|
|
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,
|
|
|
|
|
"批量执行完成",
|
|
|
|
|
"所有规程已按顺序执行完毕!"
|
|
|
|
|
)
|