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

@ -29,6 +29,8 @@ class MainLeft(QWidget):
self.protocolMag = QtWidgets.QPushButton(self) self.protocolMag = QtWidgets.QPushButton(self)
self.protocolMag.setObjectName("protocolMag") self.protocolMag.setObjectName("protocolMag")
self.procedureMag = QtWidgets.QPushButton(self)
self.procedureMag.setObjectName("protocolMag")
self.verticalLayout = QtWidgets.QVBoxLayout(self) self.verticalLayout = QtWidgets.QVBoxLayout(self)
self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setContentsMargins(0, 0, 0, 0)
@ -48,6 +50,8 @@ class MainLeft(QWidget):
self.verticalLayout.addWidget(QtWidgets.QSplitter()) self.verticalLayout.addWidget(QtWidgets.QSplitter())
self.verticalLayout.addWidget(self.protocolMag) self.verticalLayout.addWidget(self.protocolMag)
self.verticalLayout.addWidget(QtWidgets.QSplitter()) self.verticalLayout.addWidget(QtWidgets.QSplitter())
self.verticalLayout.addWidget(self.procedureMag)
self.verticalLayout.addWidget(QtWidgets.QSplitter())
self.verticalLayout.setStretch(0, 1) self.verticalLayout.setStretch(0, 1)
self.verticalLayout.setStretch(1, 4) self.verticalLayout.setStretch(1, 4)
@ -59,9 +63,9 @@ class MainLeft(QWidget):
self.verticalLayout.setStretch(7, 4) self.verticalLayout.setStretch(7, 4)
self.verticalLayout.setStretch(8, 2) self.verticalLayout.setStretch(8, 2)
self.verticalLayout.setStretch(9, 4) self.verticalLayout.setStretch(9, 4)
# self.verticalLayout.setStretch(10, 2) self.verticalLayout.setStretch(10, 2)
# self.verticalLayout.setStretch(11, 4) self.verticalLayout.setStretch(11, 4)
self.verticalLayout.setStretch(10, 40) self.verticalLayout.setStretch(12, 40)
QtCore.QMetaObject.connectSlotsByName(self) QtCore.QMetaObject.connectSlotsByName(self)
@ -71,6 +75,7 @@ class MainLeft(QWidget):
self.trendMag.setText("历史趋势") self.trendMag.setText("历史趋势")
self.userMag.setText("用户管理") self.userMag.setText("用户管理")
self.protocolMag.setText("通讯配置") self.protocolMag.setText("通讯配置")
self.procedureMag.setText("规程管理")
self.createProject.setIcon(QIcon(':/static/newH.png')) self.createProject.setIcon(QIcon(':/static/newH.png'))
# self.openProject.setIcon(QIcon(':/static/open.png')) # self.openProject.setIcon(QIcon(':/static/open.png'))
@ -78,8 +83,9 @@ class MainLeft(QWidget):
self.trendMag.setIcon(QIcon(':/static/trend.png')) self.trendMag.setIcon(QIcon(':/static/trend.png'))
self.userMag.setIcon(QIcon(':/static/userMag.png')) self.userMag.setIcon(QIcon(':/static/userMag.png'))
self.protocolMag.setIcon(QIcon(':/static/setting.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.setBtn(btn)
# self.openProject.clicked.connect(lambda:self.openProject.setIcon(QIcon(':/static/openH.png'))) # 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 re
import sys import sys
import json import json
from anyio import value # from anyio import value
import qtawesome import qtawesome
from PyQt5.QtWidgets import QApplication, QTabWidget, QWidget, QLabel, QPushButton, \ 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