0616更新

main
zcwBit 6 months ago
parent fc81413870
commit 55eb458d5b

@ -0,0 +1,652 @@
/* 规程管理系统 - 白色浅蓝色QSS样式 */
/* ==================== 全局样式 ==================== */
QWidget {
background-color: #ffffff;
color: #2c3e50;
font-family: "Microsoft YaHei", "Segoe UI", Arial, sans-serif;
font-size: 9pt;
selection-background-color: #3498db;
selection-color: #ffffff;
}
/* ==================== 主窗口 ==================== */
QMainWindow {
background-color: #f8f9fa;
border: none;
}
QMainWindow::separator {
background-color: #bdc3c7;
width: 2px;
height: 2px;
}
/* ==================== 工具栏 ==================== */
QToolBar {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #ecf0f1, stop: 1 #d5dbdb);
border: none;
spacing: 3px;
padding: 5px;
border-bottom: 2px solid #3498db;
}
QToolBar::handle {
background: #bdc3c7;
width: 8px;
margin: 2px;
}
QToolBar QToolButton {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #ffffff, stop: 1 #ecf0f1);
border: 1px solid #bdc3c7;
border-radius: 6px;
padding: 8px 12px;
margin: 2px;
color: #2c3e50;
font-weight: bold;
}
QToolBar QToolButton:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #e8f4fd, stop: 1 #d6eaff);
border: 1px solid #3498db;
box-shadow: 0 2px 4px rgba(52, 152, 219, 0.3);
}
QToolBar QToolButton:pressed {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #d6eaff, stop: 1 #c3e2ff);
border: 1px solid #2980b9;
}
QToolBar QToolButton:disabled {
background: #f5f6fa;
color: #95a5a6;
border: 1px solid #d5dbdb;
}
/* ==================== 状态栏 ==================== */
QStatusBar {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #ecf0f1, stop: 1 #d5dbdb);
border-top: 1px solid #bdc3c7;
color: #34495e;
padding: 3px;
}
QStatusBar::item {
border: none;
}
/* ==================== 按钮 ==================== */
QPushButton {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #ffffff, stop: 1 #ecf0f1);
border: 1px solid #bdc3c7;
border-radius: 8px;
padding: 10px 16px;
font-weight: bold;
color: #2c3e50;
min-width: 80px;
min-height: 30px;
}
QPushButton:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #e8f4fd, stop: 1 #d6eaff);
border: 1px solid #3498db;
box-shadow: 0 2px 8px rgba(52, 152, 219, 0.4);
}
QPushButton:pressed {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #d6eaff, stop: 1 #c3e2ff);
border: 1px solid #2980b9;
}
QPushButton:disabled {
background: #f5f6fa;
color: #95a5a6;
border: 1px solid #d5dbdb;
}
/* 特殊按钮颜色 */
QPushButton[text*="开始"], QPushButton[text*="执行"] {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #58d68d, stop: 1 #27ae60);
border: 1px solid #2ecc71;
color: #ffffff;
}
QPushButton[text*="开始"]:hover, QPushButton[text*="执行"]:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #7dcea0, stop: 1 #58d68d);
box-shadow: 0 2px 8px rgba(46, 204, 113, 0.4);
}
QPushButton[text*="停止"], QPushButton[text*="删除"] {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #ec7063, stop: 1 #e74c3c);
border: 1px solid #e67e22;
color: #ffffff;
}
QPushButton[text*="停止"]:hover, QPushButton[text*="删除"]:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #f1948a, stop: 1 #ec7063);
box-shadow: 0 2px 8px rgba(231, 76, 60, 0.4);
}
QPushButton[text*="重置"] {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #f7dc6f, stop: 1 #f39c12);
border: 1px solid #f1c40f;
color: #ffffff;
}
QPushButton[text*="重置"]:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #f8e6a0, stop: 1 #f7dc6f);
box-shadow: 0 2px 8px rgba(243, 156, 18, 0.4);
}
QPushButton[text*="报告"], QPushButton[text*="导入"] {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #bb8fce, stop: 1 #9b59b6);
border: 1px solid #af7ac5;
color: #ffffff;
}
QPushButton[text*="报告"]:hover, QPushButton[text*="导入"]:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #d2b4de, stop: 1 #bb8fce);
box-shadow: 0 2px 8px rgba(155, 89, 182, 0.4);
}
/* ==================== 标签 ==================== */
QLabel {
color: #2c3e50;
background: transparent;
font-weight: normal;
}
QLabel[text="规程分类"], QLabel[text="规程列表"], QLabel[text="测试步骤:"] {
font-size: 11pt;
font-weight: bold;
color: #3498db;
padding: 5px 0px;
}
/* ==================== 表格视图 ==================== */
QTableView {
background-color: #ffffff;
alternate-background-color: #f8f9fa;
gridline-color: #d5dbdb;
border: 1px solid #bdc3c7;
border-radius: 8px;
selection-background-color: #3498db;
selection-color: #ffffff;
}
QHeaderView::section {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #ffffff, stop: 1 #ecf0f1);
color: #2c3e50;
padding: 10px 8px;
border: none;
border-right: 1px solid #bdc3c7;
border-bottom: 2px solid #3498db;
font-weight: bold;
}
QHeaderView::section:first {
border-top-left-radius: 8px;
}
QHeaderView::section:last {
border-top-right-radius: 8px;
border-right: none;
}
QHeaderView::section:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #e8f4fd, stop: 1 #d6eaff);
}
/* ==================== 列表控件 ==================== */
QListWidget {
background-color: #ffffff;
border: 1px solid #bdc3c7;
border-radius: 8px;
padding: 5px;
outline: none;
}
QListWidget::item {
background-color: transparent;
border: none;
border-radius: 4px;
padding: 10px 8px;
margin: 2px;
color: #2c3e50;
}
QListWidget::item:hover {
background-color: #e8f4fd;
border: 1px solid #3498db;
}
QListWidget::item:selected {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #3498db, stop: 1 #2980b9);
color: #ffffff;
border: 1px solid #5dade2;
}
/* ==================== 选项卡 ==================== */
QTabWidget::pane {
border: 1px solid #bdc3c7;
border-radius: 8px;
background-color: #ffffff;
top: -1px;
}
QTabBar::tab {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #ffffff, stop: 1 #ecf0f1);
border: 1px solid #bdc3c7;
padding: 10px 20px;
margin-right: 2px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
color: #34495e;
}
QTabBar::tab:selected {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #3498db, stop: 1 #2980b9);
color: #ffffff;
border-bottom-color: #ffffff;
}
QTabBar::tab:hover:!selected {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #e8f4fd, stop: 1 #d6eaff);
}
/* ==================== 输入控件 ==================== */
QLineEdit {
background-color: #ffffff;
border: 2px solid #bdc3c7;
border-radius: 6px;
padding: 8px 12px;
color: #2c3e50;
font-size: 9pt;
}
QLineEdit:focus {
border: 2px solid #3498db;
background-color: #fdfdfe;
box-shadow: 0 0 8px rgba(52, 152, 219, 0.3);
}
QLineEdit:hover {
border: 2px solid #3498db;
}
QSpinBox {
background-color: #ffffff;
border: 2px solid #bdc3c7;
border-radius: 6px;
padding: 8px;
color: #2c3e50;
min-width: 60px;
}
QSpinBox:focus {
border: 2px solid #3498db;
background-color: #fdfdfe;
}
QSpinBox::up-button, QSpinBox::down-button {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #ffffff, stop: 1 #ecf0f1);
border: 1px solid #bdc3c7;
width: 20px;
}
QSpinBox::up-button:hover, QSpinBox::down-button:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #e8f4fd, stop: 1 #d6eaff);
}
QSpinBox::up-arrow {
image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAGCAYAAAD37n+BAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABHSURBVBiVY/z//z8DJYCJgUIwqmFUw6gGcjWQ5QdqaKBYA7ma/lOggWwN5PqBGhoo1kCuJnI1/KdAA9ka/lOggVwN5PoBAJZzBTcQ48y8AAAAAElFTkSuQmCC);
width: 8px;
height: 4px;
}
QSpinBox::down-arrow {
image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAGCAYAAAD37n+BAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABHSURBVBiVY2D4/58BFWBioBCMahgYDf8p0EC2hv8UaCBbA7ma/lOggWwN5Gqipwayff+fAg1ka/hPgQayNZCriboGAJd7BTc3gZfyAAAAAElFTkSuQmCC);
width: 8px;
height: 4px;
}
/* ==================== 复选框 ==================== */
QCheckBox {
color: #2c3e50;
spacing: 8px;
}
QCheckBox::indicator {
width: 18px;
height: 18px;
border: 2px solid #bdc3c7;
border-radius: 3px;
background-color: #ffffff;
}
QCheckBox::indicator:hover {
border: 2px solid #3498db;
background-color: #e8f4fd;
}
QCheckBox::indicator:checked {
background-color: #3498db;
border: 2px solid #2980b9;
image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAJCAYAAAAGuM1UAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABmSURBVBiVjZExDsIwEAT3JFpKSoo8gCfwCF7ACyipa3oCb+ANvIEn8AaegJYqLWnSRMmy1/YkFhftzNw9jUajUQMwBTaStpJOkmZm9jSzB/A0sy2wljS193O6nZmdgR/wBhYRcfsDGEwlvgBP2TAAAAAASUVORK5CYII=);
}
QCheckBox::indicator:checked:hover {
background-color: #5dade2;
}
/* ==================== 组合框 ==================== */
QComboBox {
background-color: #ffffff;
border: 2px solid #bdc3c7;
border-radius: 6px;
padding: 8px 12px;
color: #2c3e50;
min-width: 100px;
}
QComboBox:focus {
border: 2px solid #3498db;
background-color: #fdfdfe;
}
QComboBox::drop-down {
border: none;
width: 30px;
}
QComboBox::down-arrow {
image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAGCAYAAAD37n+BAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAAdgAAAHYBTnsmCAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAABHSURBVBiVY2D4/58BFWBioBCMahgYDf8p0EC2hv8UaCBbA7ma/lOggWwN5Gqipwayff+fAg1ka/hPgQayNZCriboGAJd7BTc3gZfyAAAAAElFTkSuQmCC);
width: 12px;
height: 8px;
}
QComboBox QAbstractItemView {
background-color: #ffffff;
border: 1px solid #bdc3c7;
border-radius: 6px;
color: #2c3e50;
selection-background-color: #3498db;
padding: 4px;
}
QComboBox QAbstractItemView::item {
height: 30px;
padding: 6px 12px;
border: none;
border-radius: 4px;
}
QComboBox QAbstractItemView::item:hover {
background-color: #e8f4fd;
}
QComboBox QAbstractItemView::item:selected {
background-color: #3498db;
color: #ffffff;
}
/* ==================== 对话框 ==================== */
QDialog {
background-color: #ffffff;
border: 1px solid #bdc3c7;
border-radius: 12px;
}
QDialogButtonBox QPushButton {
min-width: 100px;
padding: 10px 20px;
}
/* ==================== 消息框 ==================== */
QMessageBox {
background-color: #ffffff;
color: #2c3e50;
border-radius: 8px;
}
QMessageBox QLabel {
color: #2c3e50;
font-size: 10pt;
}
QMessageBox QPushButton {
min-width: 80px;
padding: 8px 16px;
}
/* ==================== 表单布局 ==================== */
QFormLayout QLabel {
font-weight: bold;
color: #3498db;
min-width: 100px;
}
/* ==================== 滚动条 ==================== */
QScrollBar:vertical {
background-color: #f8f9fa;
width: 12px;
border-radius: 6px;
margin: 0px;
}
QScrollBar::handle:vertical {
background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #d5dbdb, stop: 1 #bdc3c7);
border-radius: 6px;
min-height: 30px;
}
QScrollBar::handle:vertical:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #3498db, stop: 1 #2980b9);
}
QScrollBar::handle:vertical:pressed {
background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #2980b9, stop: 1 #21618c);
}
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
height: 0px;
background: none;
}
QScrollBar:horizontal {
background-color: #f8f9fa;
height: 12px;
border-radius: 6px;
margin: 0px;
}
QScrollBar::handle:horizontal {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #d5dbdb, stop: 1 #bdc3c7);
border-radius: 6px;
min-width: 30px;
}
QScrollBar::handle:horizontal:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #3498db, stop: 1 #2980b9);
}
QScrollBar::handle:horizontal:pressed {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #2980b9, stop: 1 #21618c);
}
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
width: 0px;
background: none;
}
/* ==================== 菜单 ==================== */
QMenu {
background-color: #ffffff;
border: 1px solid #bdc3c7;
border-radius: 8px;
padding: 4px;
color: #2c3e50;
}
QMenu::item {
background-color: transparent;
padding: 8px 16px;
border-radius: 4px;
margin: 1px;
}
QMenu::item:selected {
background-color: #3498db;
color: #ffffff;
}
QMenu::item:disabled {
color: #95a5a6;
}
QMenu::separator {
height: 1px;
background-color: #d5dbdb;
margin: 4px 8px;
}
/* ==================== 进度条 ==================== */
QProgressBar {
background-color: #ffffff;
border: 1px solid #bdc3c7;
border-radius: 8px;
text-align: center;
color: #2c3e50;
font-weight: bold;
}
QProgressBar::chunk {
background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0,
stop: 0 #3498db, stop: 1 #2980b9);
border-radius: 7px;
margin: 1px;
}
/* ==================== 工具提示 ==================== */
QToolTip {
background-color: #ffffff;
color: #2c3e50;
border: 1px solid #3498db;
border-radius: 6px;
padding: 8px;
font-size: 9pt;
opacity: 240;
}
/* ==================== 分割器 ==================== */
QSplitter::handle {
background-color: #d5dbdb;
}
QSplitter::handle:horizontal {
width: 3px;
border-radius: 1px;
}
QSplitter::handle:vertical {
height: 3px;
border-radius: 1px;
}
QSplitter::handle:hover {
background-color: #3498db;
}
/* ==================== 特殊样式 ==================== */
/* 成功状态的行 */
QTableView::item[success="true"] {
background-color: rgba(46, 204, 113, 0.1);
color: #27ae60;
}
/* 失败状态的行 */
QTableView::item[success="false"] {
background-color: rgba(231, 76, 60, 0.1);
color: #e74c3c;
}
/* 主步骤样式 */
QTableView::item[main_step="true"] {
background-color: rgba(52, 152, 219, 0.1);
font-weight: bold;
}
/* 状态指示器 */
.status-success {
color: #27ae60;
font-weight: bold;
}
.status-error {
color: #e74c3c;
font-weight: bold;
}
.status-warning {
color: #f39c12;
font-weight: bold;
}
.status-info {
color: #3498db;
font-weight: bold;
}
/* ==================== 动画效果 ==================== */
QPushButton {
transition: all 0.3s ease;
}
QToolButton {
transition: all 0.3s ease;
}
QLineEdit {
transition: border-color 0.3s ease, background-color 0.3s ease;
}
QListWidget::item {
transition: background-color 0.2s ease, border-color 0.2s ease;
}
QTableView::item {
transition: background-color 0.2s ease;
}

@ -19,6 +19,7 @@ from model.ClientModel.Client import Client
from utils import Globals
from utils.DBModels.InitParameterDB import InitParameterDB
from UI.ProfibusWidgets.ProfibusWindow import ProfibusWidgets
from UI.ProcedureManager.ProcedureManager import ProcedureManager
class CommonHelper:
def __init__(self):
@ -76,6 +77,7 @@ class MainWindow(QMainWindow):
self.projectWidget = ProjectWidgets()
self.userWidget = UserWidgets()
self.procedureManagerWidget = ProcedureManager()
self.ModbusTcpMasterWidget = VarWidgets('ModbusTcpMaster')
self.ModbusTcpSlaveWidget = VarWidgets('ModbusTcpSlave')
@ -100,6 +102,7 @@ class MainWindow(QMainWindow):
self.analogWidget.setObjectName('analogWidget')
self.hartsimulateWidget.setObjectName('hartsimulateWidget')
self.varManageTabWidget = QTabWidget()
self.varManageTabWidget.setObjectName("varManageTabWidget")
self.varManageTabWidget.tabBar().setObjectName('varManageTabBar')
@ -121,7 +124,7 @@ class MainWindow(QMainWindow):
self.rightWidget.addWidget(self.trendWidget)
self.rightWidget.addWidget(self.userWidget)
self.rightWidget.addWidget(self.SettingWidget)
self.rightWidget.addWidget(self.procedureManagerWidget)
self.rightWidget.widget(1)
self.leftWidget.createProject.clicked.connect(lambda: self.exButtonClicked(0))
@ -129,6 +132,7 @@ class MainWindow(QMainWindow):
self.leftWidget.trendMag.clicked.connect(self.reFreshTrendWidget)
self.leftWidget.userMag.clicked.connect(lambda: self.exButtonClicked(3))
self.leftWidget.protocolMag.clicked.connect(self.showSetting)
self.leftWidget.procedureMag.clicked.connect(lambda: self.initProcedureDB())
self.setCentralWidget(self.centralwidget)
self.setWindowOpacity(0.995) # 设置窗口透明度
@ -158,35 +162,37 @@ class MainWindow(QMainWindow):
self.setWindowFlag(Qt.FramelessWindowHint)
def exButtonClicked(self, index):
if not Globals.getValue('currentPro') and index != 0:
if Globals.getValue('currentPro') == -1 and index not in [0, 3]:
return -1
print(index, Globals.getValue('currentPro'))
self.rightWidget.setCurrentIndex(index)
def showSetting(self):
proType = Globals.getValue('currentProType')
if proType == -1:
if Globals.getValue('currentPro') == -1:
return
self.SettingWidget.setupUI()
self.rightWidget.setCurrentIndex(4)
def varShow(self):
proType = Globals.getValue('currentProType')
if proType == -1:
return
self.exButtonClicked(1)
Globals.setValue('SearchWidget', 1)
def reFreshTrendWidget(self):
proType = Globals.getValue('currentProType')
if proType == -1:
if Globals.getValue('currentPro') == -1:
return
self.trendWidget.getMems()
self.exButtonClicked(2)
def initProcedureDB(self):
if Globals.getValue('currentPro') == -1:
return
self.procedureManagerWidget.initDB()
self.procedureManagerWidget.initUI()
self.exButtonClicked(5)
def enum(self,**enums):
return type('Enum', (), enums)

@ -29,6 +29,8 @@ class MainLeft(QWidget):
self.protocolMag = QtWidgets.QPushButton(self)
self.protocolMag.setObjectName("protocolMag")
self.procedureMag = QtWidgets.QPushButton(self)
self.procedureMag.setObjectName("protocolMag")
self.verticalLayout = QtWidgets.QVBoxLayout(self)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
@ -48,6 +50,8 @@ class MainLeft(QWidget):
self.verticalLayout.addWidget(QtWidgets.QSplitter())
self.verticalLayout.addWidget(self.protocolMag)
self.verticalLayout.addWidget(QtWidgets.QSplitter())
self.verticalLayout.addWidget(self.procedureMag)
self.verticalLayout.addWidget(QtWidgets.QSplitter())
self.verticalLayout.setStretch(0, 1)
self.verticalLayout.setStretch(1, 4)
@ -59,9 +63,9 @@ class MainLeft(QWidget):
self.verticalLayout.setStretch(7, 4)
self.verticalLayout.setStretch(8, 2)
self.verticalLayout.setStretch(9, 4)
# self.verticalLayout.setStretch(10, 2)
# self.verticalLayout.setStretch(11, 4)
self.verticalLayout.setStretch(10, 40)
self.verticalLayout.setStretch(10, 2)
self.verticalLayout.setStretch(11, 4)
self.verticalLayout.setStretch(12, 40)
QtCore.QMetaObject.connectSlotsByName(self)
@ -71,6 +75,7 @@ class MainLeft(QWidget):
self.trendMag.setText("历史趋势")
self.userMag.setText("用户管理")
self.protocolMag.setText("通讯配置")
self.procedureMag.setText("规程管理")
self.createProject.setIcon(QIcon(':/static/newH.png'))
# self.openProject.setIcon(QIcon(':/static/open.png'))
@ -78,8 +83,9 @@ class MainLeft(QWidget):
self.trendMag.setIcon(QIcon(':/static/trend.png'))
self.userMag.setIcon(QIcon(':/static/userMag.png'))
self.protocolMag.setIcon(QIcon(':/static/setting.png'))
self.procedureMag.setIcon(QIcon(':/static/procedure.png'))
for btn in [self.createProject, self.varMag, self.trendMag, self.userMag, self.protocolMag]:
for btn in [self.createProject, self.varMag, self.trendMag, self.userMag, self.protocolMag, self.procedureMag]:
self.setBtn(btn)
# self.openProject.clicked.connect(lambda:self.openProject.setIcon(QIcon(':/static/openH.png')))

File diff suppressed because it is too large Load Diff

@ -1,7 +1,7 @@
import re
import sys
import json
from anyio import value
# from anyio import value
import qtawesome
from PyQt5.QtWidgets import QApplication, QTabWidget, QWidget, QLabel, QPushButton, \

@ -0,0 +1,204 @@
import json
import re
from datetime import datetime
from PyQt5.QtCore import Qt, QTimer, QAbstractTableModel, QModelIndex, QPoint, QSize
from PyQt5.QtGui import QFont, QBrush, QColor
from openpyxl import load_workbook
class ExcelParser:
@staticmethod
def parseProcedure(filePath):
wb = load_workbook(filename=filePath)
if '测试脚本' in wb.sheetnames:
sheet = wb['测试脚本']
else:
sheet = wb.active
specInfo = {
"规程名称": sheet['B1'].value,
"规程编号": sheet['D1'].value,
"规程类型": sheet['F1'].value
}
testCaseInfo = {
"测试用例": sheet['B2'].value,
"用例编号": sheet['D2'].value,
"工况描述": sheet['H2'].value
}
testSteps = []
currentStep = None
stepCounter = 0
for rowIdx in range(5, sheet.max_row + 1):
cellA = sheet[f'A{rowIdx}'].value
cellB = sheet[f'B{rowIdx}'].value
cellC = sheet[f'C{rowIdx}'].value
if cellB is None and cellA is None:
continue
if cellA and re.match(r'STEP\d+[:]', str(cellA)):
if currentStep:
testSteps.append(currentStep)
stepCounter += 1
stepName = str(cellA).replace('', ':').strip()
stepDesc = str(cellB).replace('\n', ' ').strip() if cellB else ""
currentStep = {
"步骤ID": stepName,
"步骤描述": stepDesc,
"操作类型": cellC if cellC else "",
"预期结果": sheet[f'D{rowIdx}'].value,
"子步骤": []
}
elif currentStep and cellA and str(cellA).isdigit():
subStep = {
"序号": int(cellA),
"操作": str(cellB).replace('\n', ' ').strip() if cellB else "",
"操作类型": cellC if cellC else "",
"预期结果": sheet[f'D{rowIdx}'].value,
"实际结果": sheet[f'E{rowIdx}'].value,
"一致性": sheet[f'F{rowIdx}'].value if sheet[f'F{rowIdx}'].value == "" else "",
"测试时间": sheet[f'H{rowIdx}'].value,
"备注": sheet[f'I{rowIdx}'].value
}
currentStep["子步骤"].append(subStep)
if currentStep:
testSteps.append(currentStep)
return {
"文件路径": filePath,
"规程信息": specInfo,
"测试用例信息": testCaseInfo,
"测试步骤": testSteps
}
class StepTableModel(QAbstractTableModel):
columns = ['序号', '实验步骤', '执行时间', '是否与预期一致', '实际结果', '备注']
def __init__(self, testSteps):
super().__init__()
self.stepData = []
self.stepIndex = 0
for mainStep in testSteps:
self.stepData.append({
'id': self.stepIndex,
'isMain': True,
'stepId': mainStep['步骤ID'],
'description': mainStep['步骤描述'],
'executed': False,
'time': None,
'result': None,
'note': "" # 新增备注字段,主步骤默认为空
})
self.stepIndex += 1
for subStep in mainStep['子步骤']:
self.stepData.append({
'id': self.stepIndex,
'isMain': False,
'stepId': f"{mainStep['步骤ID']}{subStep['序号']}",
'description': subStep['操作'],
'executed': False,
'time': None,
'result': None,
'note': subStep['备注'] # 新增从Excel解析的备注字段
})
self.stepIndex += 1
def rowCount(self, parent=QModelIndex()):
return len(self.stepData)
def columnCount(self, parent=QModelIndex()):
return len(self.columns)
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return None
row = index.row()
col = index.column()
step = self.stepData[row]
if role == Qt.DisplayRole:
if col == 0:
return step['stepId']
elif col == 1:
return step['description']
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']]
elif col == 4:
return {True: '成功', False: '失败', None: ''}[step['result']]
elif col == 5:
return step['note'] if step['note'] else ''
elif role == Qt.BackgroundRole:
if step['executed']:
if step['result']:
return QBrush(QColor(144, 238, 144))
else:
return QBrush(QColor(255, 182, 193))
elif step['isMain']:
return QBrush(QColor(220, 220, 220))
elif role == Qt.FontRole and step['isMain']:
font = QFont()
font.setBold(True)
return font
return None
def headerData(self, section, orientation, role):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.columns[section]
return None
def getStepInfo(self, row):
if 0 <= row < len(self.stepData):
return self.stepData[row]
return None
def getFullStepInfo(self, row):
"""获取完整的步骤信息"""
if 0 <= row < len(self.stepData):
return self.stepData[row]
return None
def updateStepResult(self, row, result, time):
if 0 <= row < len(self.stepData):
self.stepData[row]['executed'] = True
self.stepData[row]['time'] = time
self.stepData[row]['result'] = result
self.dataChanged.emit(self.index(row, 0),
self.index(row, self.columnCount()-1),
[Qt.DisplayRole, Qt.BackgroundRole])
return True
return False
def resetExecutionState(self):
"""只重置执行状态(颜色),不清除时间和结果"""
for step in self.stepData:
step['executed'] = False
step['result'] = None
self.dataChanged.emit(self.index(0, 0),
self.index(self.rowCount()-1, self.columnCount()-1),
[Qt.BackgroundRole])
def resetAll(self):
"""完全重置所有状态(包括时间和结果)"""
for step in self.stepData:
step.update({
'executed': False,
'time': None,
'result': None
})
self.dataChanged.emit(self.index(0, 0),
self.index(self.rowCount()-1, self.columnCount()-1),
[Qt.DisplayRole, Qt.BackgroundRole])

@ -0,0 +1,302 @@
import sqlite3
import json
import os # 添加os模块导入
from datetime import datetime
from utils import Globals
class DatabaseManager:
def __init__(self, dbPath="procedures.db"):
# 修改:使用跨平台的路径构造方式
project_dir = os.path.join(os.getcwd(), 'project', str(Globals.getValue('currentPro')))
db_dir = os.path.join(project_dir, 'db')
# 确保目录存在
os.makedirs(db_dir, exist_ok=True)
self.dbPath = os.path.join(db_dir, 'procedures.db')
print(f"数据库路径: {self.dbPath}")
self.conn = sqlite3.connect(self.dbPath)
self.cursor = self.conn.cursor()
self.createTables()
def createTables(self):
# 创建分类表
self.cursor.execute("""
CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL
)
""")
# 创建规程表
self.cursor.execute("""
CREATE TABLE IF NOT EXISTS procedures (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER,
name TEXT NOT NULL,
number TEXT,
type TEXT,
content TEXT, -- 存储解析后的JSON数据
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(category_id) REFERENCES categories(id)
)
""")
# 创建规程执行历史表(每次执行记录一条)
self.cursor.execute("""
CREATE TABLE IF NOT EXISTS procedure_execution_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
procedure_id INTEGER NOT NULL,
start_time TEXT NOT NULL,
end_time TEXT,
result BOOLEAN, -- 总体结果
report_path TEXT, -- 报告路径
step_results TEXT -- 新增存储所有步骤执行结果的JSON
)
""")
# 插入默认分类
self.cursor.execute("""
INSERT OR IGNORE INTO categories (name) VALUES
('默认分类'),
('紧急规程'),
('测试规程'),
('维护规程')
""")
# 确保procedures表有report_path列
try:
self.cursor.execute("ALTER TABLE procedures ADD COLUMN report_path TEXT DEFAULT ''")
except sqlite3.OperationalError:
pass # 列已存在则忽略错误
# 创建操作历史表
self.cursor.execute("""
CREATE TABLE IF NOT EXISTS operation_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
execution_id INTEGER NOT NULL,
operation_type TEXT NOT NULL,
operation_detail TEXT,
operation_time TEXT NOT NULL,
FOREIGN KEY(execution_id) REFERENCES procedure_execution_history(id)
)
""")
self.conn.commit()
def getCategories(self):
self.cursor.execute("SELECT id, name FROM categories ORDER BY name")
return self.cursor.fetchall()
def addCategory(self, name):
try:
self.cursor.execute("INSERT INTO categories (name) VALUES (?)", (name,))
self.conn.commit()
return True
except sqlite3.IntegrityError:
return False
def deleteCategory(self, categoryId):
# 检查是否为默认分类
self.cursor.execute("SELECT name FROM categories WHERE id = ?", (categoryId,))
categoryName = self.cursor.fetchone()[0]
if categoryName == "默认分类":
return False # 默认分类不可删除
self.cursor.execute("SELECT id FROM categories WHERE name = '默认分类'")
defaultCatId = self.cursor.fetchone()[0]
self.cursor.execute("""
UPDATE procedures
SET category_id = ?
WHERE category_id = ?
""", (defaultCatId, categoryId))
self.cursor.execute("DELETE FROM categories WHERE id = ?", (categoryId,))
self.conn.commit()
return True
def addProcedure(self, categoryId, name, number, type, content, reportPath=""):
"""添加新规程到数据库"""
contentJson = json.dumps(content, ensure_ascii=False)
self.cursor.execute("""
INSERT INTO procedures (category_id, name, number, type, content, report_path)
VALUES (?, ?, ?, ?, ?, ?)
""", (categoryId, name, number, type, contentJson, reportPath))
self.conn.commit()
return self.cursor.lastrowid
def getProcedures(self, categoryId=None):
if categoryId:
self.cursor.execute("""
SELECT id, name, number, type, created_at
FROM procedures
WHERE category_id = ?
ORDER BY name
""", (categoryId,))
else:
self.cursor.execute("""
SELECT id, name, number, type, created_at
FROM procedures
ORDER BY name
""")
return self.cursor.fetchall()
def getProcedureContent(self, procedureId):
self.cursor.execute("SELECT content FROM procedures WHERE id = ?", (procedureId,))
result = self.cursor.fetchone()
if result:
return json.loads(result[0])
return None
def deleteProcedure(self, procedureId):
self.cursor.execute("DELETE FROM procedures WHERE id = ?", (procedureId,))
self.conn.commit()
return self.cursor.rowcount > 0
def insertProcedureExecution(self, procedureId, startTime, result=None, reportPath="", stepResults="[]"):
self.cursor.execute("""
INSERT INTO procedure_execution_history
(procedure_id, start_time, result, report_path, step_results)
VALUES (?, ?, ?, ?, ?)
""", (procedureId, startTime, result, reportPath, stepResults))
self.conn.commit()
return self.cursor.lastrowid
def updateStepResults(self, executionId, stepResults):
self.cursor.execute("""
UPDATE procedure_execution_history
SET step_results = ?
WHERE id = ?
""", (json.dumps(stepResults), executionId))
self.conn.commit()
return self.cursor.rowcount > 0
def getStepResults(self, executionId):
"""根据执行ID获取步骤结果 - 修正SQL语句"""
# 修正:使用正确的列名 execution_id -> id
self.cursor.execute("SELECT step_results FROM procedure_execution_history WHERE id=?", (executionId,))
row = self.cursor.fetchone()
if row:
return json.loads(row[0])
return None
def getExecutionHistory(self, filterText=None):
query = """
SELECT
peh.id,
p.name || '(' || p.number || ')' AS full_name, -- 拼接规程全名
p.type,
peh.start_time,
peh.report_path
FROM procedure_execution_history peh
JOIN procedures p ON peh.procedure_id = p.id
WHERE peh.report_path IS NOT NULL
"""
params = []
if filterText:
query += " AND (p.name LIKE ? OR p.number LIKE ? OR p.type LIKE ?)"
searchTerm = f"%{filterText}%"
params = [searchTerm, searchTerm, searchTerm]
self.cursor.execute(query, params)
return self.cursor.fetchall()
def updateExecutionReportPath(self, executionId, reportPath):
self.cursor.execute("""
UPDATE procedure_execution_history
SET report_path = ?
WHERE id = ?
""", (reportPath, executionId))
self.conn.commit()
return self.cursor.rowcount > 0
def deleteExecutionHistory(self, executionIds):
"""删除指定的执行历史记录及其相关操作历史 - 方法名已修正"""
if not executionIds:
return False
try:
# 转换为整数列表
ids = [int(id) for id in executionIds]
# 删除操作历史记录
self.cursor.executemany(
"DELETE FROM operation_history WHERE execution_id = ?",
[(id,) for id in ids]
)
# 删除执行历史记录
self.cursor.executemany(
"DELETE FROM procedure_execution_history WHERE id = ?",
[(id,) for id in ids]
)
self.conn.commit()
return True
except Exception as e:
print(f"删除执行历史失败: {str(e)}")
return False
def getExecutionDetails(self, executionId):
"""获取执行记录的完整详情"""
try:
# 获取执行记录基本信息
self.cursor.execute("""
SELECT
peh.id,
p.id AS procedure_id,
p.name AS procedure_name,
p.content AS procedure_content,
peh.step_results
FROM procedure_execution_history peh
JOIN procedures p ON peh.procedure_id = p.id
WHERE peh.id = ?
""", (executionId,))
row = self.cursor.fetchone()
if not row:
return None
# 解析数据
details = {
"execution_id": row[0],
"procedure_id": row[1],
"procedure_name": row[2],
"procedure_content": json.loads(row[3]),
"step_results": json.loads(row[4])
}
return details
except Exception as e:
print(f"获取执行详情失败: {str(e)}")
return None
def updateProcedureCategory(self, procedureId, newCategoryId):
"""更新规程的分类"""
try:
self.cursor.execute("""
UPDATE procedures
SET category_id = ?
WHERE id = ?
""", (newCategoryId, procedureId))
self.conn.commit()
return self.cursor.rowcount > 0
except Exception as e:
print(f"更新规程分类失败: {str(e)}")
return False
def saveOperationHistory(self, executionId, operationType, operationDetail, operationTime):
"""保存操作历史记录"""
self.cursor.execute("""
INSERT INTO operation_history
(execution_id, operation_type, operation_detail, operation_time)
VALUES (?, ?, ?, ?)
""", (executionId, operationType, operationDetail, operationTime))
self.conn.commit()
return self.cursor.lastrowid
def close(self):
self.conn.close()
Loading…
Cancel
Save