Compare commits

...

2 Commits

19 changed files with 845 additions and 16 deletions

View File

@ -2,6 +2,7 @@
from PySide6.QtCore import Qt, QPropertyAnimation, QRect, QParallelAnimationGroup, QEasingCurve
from view.widgets.system_center_dialog import SystemCenterDialog
from view.widgets.bottom_control_widget import BottomControlWidget
from view.widgets.system_diagnostics_dialog import SystemDiagnosticsDialog
"""
控制主界面底部的所有按钮, 包括系统诊断、系统中心等的行为。
@ -19,11 +20,19 @@ class BottomControlController:
self.system_center_dialog.hide() # 初始隐藏 (必须)
self._init_system_center_dialog_hide_animations()
# 系统诊断弹窗
self.system_diagnostics_dialog = SystemDiagnosticsDialog(self.main_window)
self.system_diagnostics_dialog.hide()
self._init_system_diagnostics_dialog_hide_animations()
self._bind_dialog_signals()
def _bind_buttons(self):
# 底部系统中心按钮 → 触发弹窗显示/隐藏
self.bottom_control_widget.center_btn.clicked.connect(self.toggle_system_center_dialog)
# 底部系统诊断按钮 → 触发弹窗显示/隐藏
self.bottom_control_widget.diagnosis_btn.clicked.connect(self.toggle_system_diagnostics_dialog)
def _bind_dialog_signals(self):
"""绑定弹窗按钮的信号"""
@ -53,7 +62,7 @@ class BottomControlController:
self.hide_anim_group.finished.connect(self.system_center_dialog.hide)
def toggle_system_center_dialog(self):
"""切换弹窗的显示/隐藏状态"""
"""切换系统中心弹窗的显示/隐藏状态"""
if self.system_center_dialog.isVisible():
# 已显示 → 隐藏
# self.system_center_dialog.hide()
@ -94,7 +103,7 @@ class BottomControlController:
# 设置弹窗位置
self.system_center_dialog.move(dialog_x, dialog_y)
# ------------------- 业务逻辑方法-------------------
# ------------------- 系统中心弹窗业务逻辑-------------------
def handle_sys_setting(self):
"""系统设置按钮的业务逻辑"""
# print("执行系统设置逻辑:如打开系统配置窗口、修改参数等")
@ -108,4 +117,78 @@ class BottomControlController:
def handle_user_center(self):
"""用户中心按钮的业务逻辑"""
# print("执行用户中心逻辑:如切换用户、修改密码等")
# print("执行用户中心逻辑:如切换用户、修改密码等")
# ------------------- 系统诊断弹窗逻辑-------------------
def _init_system_diagnostics_dialog_hide_animations(self):
"""初始化系统诊断弹窗隐藏动画(与显示动画反向:滑出+淡出)"""
# 1. 淡出动画(与显示动画时长一致)
self.dia_hide_opacity_anim = QPropertyAnimation(
self.system_diagnostics_dialog, b"windowOpacity", self.system_diagnostics_dialog
)
self.dia_hide_opacity_anim.setDuration(300) # 显示动画为400ms
self.dia_hide_opacity_anim.setStartValue(1.0)
self.dia_hide_opacity_anim.setEndValue(0.0)
# 2. 位置动画从当前位置滑出到下方100px与显示动画反向
self.dia_hide_pos_anim = QPropertyAnimation(
self.system_diagnostics_dialog, b"geometry", self.system_diagnostics_dialog
)
self.dia_hide_pos_anim.setDuration(300)
self.dia_hide_pos_anim.setEasingCurve(QEasingCurve.InQuart) # 滑出曲线与显示反向
# 3. 组合动画(同时执行滑出和淡出)
self.dia_hide_anim_group = QParallelAnimationGroup(self.system_diagnostics_dialog)
self.dia_hide_anim_group.addAnimation(self.dia_hide_opacity_anim)
self.dia_hide_anim_group.addAnimation(self.dia_hide_pos_anim)
# 动画结束后强制隐藏弹窗
self.dia_hide_anim_group.finished.connect(self.system_diagnostics_dialog.hide)
def toggle_system_diagnostics_dialog(self):
"""切换系统诊断弹窗的显示/隐藏状态"""
if self.system_diagnostics_dialog.isVisible():
# 已显示 → 执行隐藏动画
self._start_diagnostics_hide_animation()
else:
# 未显示 → 计算位置并显示(触发显示动画)
self._calc_system_diagnostics_dialog_position()
self.system_diagnostics_dialog.show()
def _calc_system_diagnostics_dialog_position(self):
"""计算系统诊断弹窗位置(显示在诊断按钮上方,与中心弹窗布局一致)"""
btn = self.bottom_control_widget.diagnosis_btn # 诊断按钮
bottom_widget = self.bottom_control_widget
# 计算按钮在主窗口中的绝对位置
bottom_pos_rel_main = bottom_widget.pos() # 底部控件相对于主窗口的位置
btn_pos_rel_bottom = btn.pos() # 诊断按钮相对于底部控件的位置
btn_pos_rel_main = bottom_pos_rel_main + btn_pos_rel_bottom # 诊断按钮在主窗口中的绝对位置
# 计算弹窗坐标(显示在按钮上方,水平居中对齐)
btn_width = btn.width()
dialog_size = self.system_diagnostics_dialog.size()
# 水平方向:与按钮居中对齐
# dialog_x = btn_pos_rel_main.x() + (btn_width - dialog_size.width()) // 2
# dialog_x = btn_pos_rel_main.x() + (btn_width - dialog_size.width())
dialog_x = btn_pos_rel_main.x() # 与系统诊断按钮的左边平齐
# 垂直方向在按钮上方与按钮保持10px间距
# dialog_y = btn_pos_rel_main.y() - dialog_size.height() - 10
dialog_y = btn_pos_rel_main.y() - dialog_size.height()
# 设置弹窗位置(动画会基于此位置执行滑入效果)
self.system_diagnostics_dialog.move(dialog_x, dialog_y)
def _start_diagnostics_hide_animation(self):
"""启动系统诊断弹窗的隐藏动画(滑出+淡出)"""
current_geo = self.system_diagnostics_dialog.geometry() # 当前位置和尺寸
# 计算隐藏动画终点当前位置下方100px与显示动画起点对应
end_rect = QRect(
current_geo.x(),
current_geo.y() + 100, # 向下滑出100px
current_geo.width(),
current_geo.height()
)
# 设置动画参数并启动
self.dia_hide_pos_anim.setStartValue(current_geo)
self.dia_hide_pos_anim.setEndValue(end_rect)
self.dia_hide_anim_group.start()

View File

@ -18,6 +18,7 @@ class MainController:
def showMainWindow(self):
self.main_window.showFullScreen()
# self.main_window.show()
self.main_window.dispatch_task_widget.set_task_time("task1","15:44 PM")
self.main_window.dispatch_task_widget.set_task_time("task2","17:37 PM")
self.main_window.segment_task_widget.set_task_time("task1","15:38 PM")

BIN
images/关闭图标.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
images/详情标题.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -92,4 +92,19 @@ class ImagePaths:
ROUND_BTN_BG2 = ":/icons/images/圆形按钮背景2.png"
# 功能:主界面相关
MAIN_INTERFACE_BACKGROUND = ":/icons/images/主界面背景.png"
MAIN_INTERFACE_BACKGROUND = ":/icons/images/主界面背景.png"
# 功能: 系统诊断弹窗
SYSTEM_DIAGNOSTICS_POPUP_BG = "images/系统诊断弹出背景.png"
SYSTEM_DIAGNOSTICS_BOX = "images/系统诊断小框.png"
SYSTEM_DIAGNOSTICS_STATUS_GREEN = "images/系统诊断状态绿.png"
SYSTEM_DIAGNOSTICS_STATUS_YELLOW = "images/系统诊断状态黄.png"
SYSTEM_DIAGNOSTICS_STATUS_RED = "images/系统诊断状态红.png"
SYSTEM_DIAGNOSTICS_MS_BG = "images/系统诊断毫秒背景.png"
SYSTEM_DIAGNOSTICS_DROPDOWN_ARROW = "images/系统诊断下拉箭头.png"
# 功能:管片任务详情按钮弹窗
SEGMENT_DETAILS_POPUP_BG = "images/详情弹出背景.png"
SEGMENT_DETAILS_TITLE_BG = "images/详情标题.png"
SEGMENT_DETAILS_INFO_BAR = "images/管片任务信息栏.png"
SEGMENT_DETAILS_CLOSE_ICON = "images/关闭图标.png"

View File

@ -21,6 +21,8 @@ from .widgets.bottom_control_widget import BottomControlWidget
import resources.resources_rc
from utils.image_paths import ImagePaths
from .widgets.segment_details_dialog import SegmentDetailsDialog
class MainWindow(QWidget):
def __init__(self):
@ -34,21 +36,27 @@ class MainWindow(QWidget):
# 安装事件过滤器,处理计划方量的 QLineEdit的失去焦点事件
self.installEventFilter(self)
# 连接槽函数
def connectSignalToSlot(self):
# 可添加信号槽连接
# self.system_button_widget.buttons["系统启动"].clicked.connect(self.handleSystemStart)
# self.system_button_widget.buttons["系统停止"].clicked.connect(self.handleSystemStop)
pass
self.conveyor_system_widget.left_btn.clicked.connect(self.handleHopperMoveLeft)
self.conveyor_system_widget.right_btn.clicked.connect(self.handleHopperMoveRight)
# 传送带部分的按钮
self.conveyor_system_widget.left_btn.clicked.connect(self.handleHopperMoveLeft) # 传送带下的左移按钮
self.conveyor_system_widget.right_btn.clicked.connect(self.handleHopperMoveRight) # 传送带下的右移按钮
# 管片任务详情
self.segment_task_widget.task_details_signal.connect(self.handleSegmentTaskDetails) # 管片任务详情按钮
def handleSystemStart(self):
# 测试
# 测试系统开启,进度条动画
self.production_progress.testProgress(60)
self.arc_progress.testProgress(60)
def handleSystemStop(self):
# 测试
# 测试系统停止,进度条动画
self.production_progress.animation.stop()
self.arc_progress.animation.stop()
@ -63,7 +71,8 @@ class MainWindow(QWidget):
# self.setStyleSheet("background-color: #ffffff;") # #001558
# Qt.FramelessWindowHint
# self.setWindowFlags(Qt.FramelessWindowHint)
# 没有顶部的白色边框
self.setWindowFlags(Qt.FramelessWindowHint) # 无边框
# 设置主界面背景图片
try:
@ -100,6 +109,7 @@ class MainWindow(QWidget):
self.dispatch_task_widget.set_task_id("task2", "PD0002")
self.dispatch_task_widget.set_task_id("task3", "PD0003")
# 读取数据库,初始化 管片任务的数据
from busisness.blls import ArtifactBll, PDRecordBll
artifact_dal = ArtifactBll()
artifacts = artifact_dal.get_artifact_task()
@ -220,6 +230,17 @@ class MainWindow(QWidget):
# 料斗右移完成,恢复料斗左移按钮
QTimer.singleShot(2100, lambda: self.conveyor_system_widget.left_btn.setEnabled(True))
def handleSegmentTaskDetails(self, segment_task_name:str):
# 管片任务名 task1、task2、task3 (分别对应第一条管片任务、 第二条管片任务...)
print("main_window: handleSegmentTaskDetails", segment_task_name)
# 显示管片任务详情对话框
segment_details_dialog = SegmentDetailsDialog(self)
# 这里可以设置对话框显示的内容 如 set_segment_id
# segment_details_dialog.set_segment_id("9999999999")
segment_details_dialog.show()
# 更新 派单任务widget的坐标
def update_dispatch_task_position(self):
# 方法1获取模具车控件左上角坐标相对于父控件

View File

@ -0,0 +1,315 @@
from PySide6.QtWidgets import (
QApplication,
QDialog,
QVBoxLayout,
QHBoxLayout,
QGridLayout,
QLabel,
QWidget,
QPushButton
)
from PySide6.QtGui import QPixmap, QFont, QPainter, QIcon
from PySide6.QtCore import Qt
import sys
from utils.image_paths import ImagePaths
"""
管片任务详情的弹出窗口: 点击管片任务的详情按钮之后弹出
"""
class SegmentDetailsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setAttribute(Qt.WA_TranslucentBackground)
# 初始化存储需要修改的控件
self.id_value_label = None # 管片ID值标签
self.left_cells = [] # 左列单元格列表每个元素是包含label和value的widget
self.right_cells = [] # 右列单元格列表
self._init_ui()
def _init_ui(self):
# 基础设置:无边框+窗口尺寸由背景图决定
self.setWindowFlags(Qt.FramelessWindowHint)
self._load_background()
# 主布局:
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(32, 20, 32, 50)
main_layout.setSpacing(0)
# 1. 顶部区域(标题 + 关闭按钮)
self._add_top_area(main_layout)
# 2. 管片ID区域保存ID值标签引用
self._add_segment_id_area(main_layout)
# 3. 网格信息区域(保存左右列单元格引用)
self._add_grid_info_area(main_layout)
def _load_background(self):
self.bg_pixmap = QPixmap(ImagePaths.SEGMENT_DETAILS_POPUP_BG)
if self.bg_pixmap.isNull():
print("错误:详情弹出背景.png 加载失败,请检查路径!")
self.setFixedSize(800, 600)
else:
self.setFixedSize(self.bg_pixmap.size())
def _add_top_area(self, parent_layout):
"""创建包含标题和关闭按钮的顶部水平布局"""
top_layout = QHBoxLayout()
top_layout.setContentsMargins(0, 0, 0, 36) # 保持原标题下方36px间距
top_layout.setSpacing(0)
# 左侧弹簧(让标题居中)
top_layout.addStretch()
# 标题标签(复用原标题逻辑)
title_label = QLabel("管片任务")
font = QFont()
font.setPixelSize(24)
font.setLetterSpacing(QFont.AbsoluteSpacing, 2)
font.setBold(True)
title_label.setFont(font)
title_label.setStyleSheet("color: #13fffc; font-weight: Bold;")
title_label.setAlignment(Qt.AlignCenter)
top_layout.addWidget(title_label)
# 右侧:关闭按钮
self._create_close_button(top_layout)
parent_layout.addLayout(top_layout)
# 新增:创建关闭按钮
def _create_close_button(self, parent_layout):
"""创建36x36关闭按钮"""
self.close_btn = QPushButton()
self.close_btn.setFixedSize(36, 36) # 固定尺寸18x18
# 加载关闭图标
close_icon = QPixmap(ImagePaths.SEGMENT_DETAILS_CLOSE_ICON)
if not close_icon.isNull():
# 设置图标并自适应按钮大小
self.close_btn.setIcon(QIcon(close_icon))
# 样式设置:默认透明背景,悬停红色背景
self.close_btn.setStyleSheet("""
QPushButton {
background-color: transparent;
border: none;
padding: 0px;
}
QPushButton:hover {
background-color: red;
border-radius: 2px;
}
""")
# 点击事件:关闭窗口
self.close_btn.clicked.connect(self.close)
# 添加到布局(与标题保持间距)
parent_layout.addStretch() # 右侧弹簧,确保按钮靠右
parent_layout.addWidget(self.close_btn)
def _add_segment_id_area(self, parent_layout):
id_layout = QHBoxLayout()
# 左侧管片ID标签
id_label = QLabel("管片ID")
id_label.setFixedSize(318, 32)
id_font = QFont()
id_font.setPixelSize(18)
id_font.setLetterSpacing(QFont.AbsoluteSpacing, 2)
id_font.setBold(True)
id_label.setFont(id_font)
id_label.setStyleSheet(f"""
background-image: url({ImagePaths.SEGMENT_DETAILS_TITLE_BG});
background-repeat: no-repeat;
background-position: center;
color: #13ffff;
""")
id_label.setContentsMargins(16, 0, 0, 0)
id_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
# 右侧管片ID值保存引用到实例变量
self.id_value_label = QLabel("346482967298119")
value_font = QFont()
value_font.setPixelSize(18)
value_font.setBold(True)
value_font.setLetterSpacing(QFont.AbsoluteSpacing, 2)
self.id_value_label.setFont(value_font)
self.id_value_label.setStyleSheet("color: #13ffff;")
self.id_value_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
id_layout.addWidget(id_label)
id_layout.addStretch()
id_layout.addWidget(self.id_value_label)
id_layout.setContentsMargins(0, 0, 0, 16)
parent_layout.addLayout(id_layout)
def _add_grid_info_area(self, parent_layout):
grid_layout = QGridLayout()
grid_layout.setSpacing(12)
# 初始化显示的数据
# 左侧信息条目
left_info_items = [
("管片编号", "QR1B32000153AD"),
("管片副标识", "QR1B32000153AD"),
("生产环号", "QR1B32000153AD"),
("模具编号", "QR1B32000153AD"),
("骨架编号", "QR1B32000153AD"),
("环类型编号", "QR1B32000153AD"),
("尺寸规格", "QR1B32000153AD"),
]
# 右侧信息条目
right_info_items = [
("分块号", "QR3143243423543254"),
("出洞环标记", "QR3143243423543254"),
("注浆管标记", "QR3143243423543254"),
("聚丙烯纤维标记", "QR3143243423543254"),
("浇筑方量", "QR3143243423543254"),
("任务单号", "QR3143243423543254"),
("埋深", "QR3143243423543254"),
]
# 填充左列并保存单元格引用
self.left_cells.clear() # 清空列表
for row, (label_text, value_text) in enumerate(left_info_items):
cell_widget = self._create_info_cell(label_text, value_text)
self.left_cells.append(cell_widget) # 保存到列表
grid_layout.addWidget(cell_widget, row, 0)
# 填充右列并保存单元格引用
self.right_cells.clear() # 清空列表
for row, (label_text, value_text) in enumerate(right_info_items):
cell_widget = self._create_info_cell(label_text, value_text)
self.right_cells.append(cell_widget) # 保存到列表
grid_layout.addWidget(cell_widget, row, 1)
parent_layout.addLayout(grid_layout)
def _create_info_cell(self, label_text, value_text):
cell_widget = QWidget()
cell_bg = QPixmap(ImagePaths.SEGMENT_DETAILS_INFO_BAR)
if not cell_bg.isNull():
cell_widget.setFixedSize(cell_bg.size())
cell_widget.setStyleSheet(f"""
background-image: url({ImagePaths.SEGMENT_DETAILS_INFO_BAR});
background-repeat: no-repeat;
background-position: Center;
""")
else:
print("警告:管片任务信息栏.png 加载失败,使用默认背景!")
cell_widget.setStyleSheet("background-color: #0a2463;")
cell_layout = QHBoxLayout(cell_widget)
cell_layout.setContentsMargins(2, 0, 0, 0)
# 左侧标签保存到cell_widget的属性中
label = QLabel(label_text)
label.setFixedSize(136, 60)
label_font = QFont()
label_font.setPixelSize(16)
label_font.setLetterSpacing(QFont.AbsoluteSpacing, 2)
label.setFont(label_font)
label.setStyleSheet("background: none; background-color: #1369b4; color: #fffffd; font-weight:Bold;")
label.setAlignment(Qt.AlignCenter)
cell_widget.label = label
# 右侧值保存到cell_widget的属性中
value = QLabel(value_text)
value_font = QFont()
value_font.setPixelSize(18)
value.setFont(value_font)
value.setStyleSheet("background: none; color: #9fbfd4;")
value.setAlignment(Qt.AlignVCenter | Qt.AlignLeft)
cell_widget.value = value
cell_layout.addWidget(label)
cell_layout.addSpacing(60)
cell_layout.addWidget(value)
return cell_widget
def paintEvent(self, event):
if not self.bg_pixmap.isNull():
painter = QPainter(self)
painter.drawPixmap(self.rect(), self.bg_pixmap)
super().paintEvent(event)
# ------------------- 对外修改接口 -------------------
# --------------修改管片任务详情中显示的值 ------------
def set_segment_id(self, new_id):
"""修改管片ID的值"""
if self.id_value_label:
self.id_value_label.setText(str(new_id))
def set_left_label(self, row, new_label_text:str):
"""
修改左列网格的标签文本 ("生产环号")
Args:
row: 左列网格行号(0-6,共7行)
new_label_text: 新的标签文字(如“管片编号”)
"""
if 0 <= row < len(self.left_cells):
cell = self.left_cells[row]
cell.label.setText(new_label_text)
def set_left_value(self, row, new_value_text:str):
"""
修改左列网格的值
Args:
row: 左列网格行号(0-6,共7行)
new_value_text: 新的值(如“FB789”)
"""
if 0 <= row < len(self.left_cells):
cell = self.left_cells[row]
cell.value.setText(new_value_text)
def set_right_label(self, row, new_label_text:str):
"""
修改右列网格的标签文本 ("任务单号")
Args:
row: 右列网格行号(0-6,共7行)
new_label_text: 新的标签文字(如“分块号”)
"""
if 0 <= row < len(self.right_cells):
cell = self.right_cells[row]
cell.label.setText(new_label_text)
def set_right_value(self, row, new_value_text:str):
"""
修改右列网格的值
Args:
row: 右列网格行号(0-6,共7行)
new_value_text: 新的值(如“FB789”)
"""
if 0 <= row < len(self.left_cells):
cell = self.right_cells[row]
cell.value.setText(new_value_text)
# 测试代码
if __name__ == "__main__":
app = QApplication(sys.argv)
dialog = SegmentDetailsDialog()
dialog.show()
# 测试修改接口
dialog.set_segment_id("999999999999999") # 修改管片ID值
# 左列修改
dialog.set_left_label(0, "新管片编号") # 修改左列第0行的标签文本
dialog.set_left_value(0, "QR6666666666666") # 修改左列第0行的值
# 右列修改
dialog.set_right_label(0, "新分块号") # 修改右列第0行的标签文本
dialog.set_right_value(0, "QR99999999999999999") # 修改右列第0行的值
sys.exit(app.exec())

View File

@ -60,10 +60,10 @@ class SystemCenterDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.init_ui()
self._init_ui()
self.init_animations() # 初始化动画
def init_ui(self):
def _init_ui(self):
# 弹窗基础设置
self.setWindowTitle("系统中心")
self.setWindowFlags(Qt.FramelessWindowHint) # 隐藏默认边框

View File

@ -0,0 +1,385 @@
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QVBoxLayout,
QGridLayout,
QLabel,
QHBoxLayout,
QListWidget,
QListWidgetItem,
QSpacerItem,
QSizePolicy,
QLineEdit,
QDialog,
)
from PySide6.QtGui import QPixmap, QFont, QColor, QTransform, QPainter
from PySide6.QtCore import (
Qt,
QPoint,
QEvent,
QPropertyAnimation,
QEasingCurve,
QRect,
QParallelAnimationGroup,
)
import sys
from utils.image_paths import ImagePaths
"""
系统诊断按钮的弹窗: 可以显示设备的状态
"""
class CustomDropdown(QWidget):
"""自定义下拉框组件"""
def __init__(self, options, arrow_img_path, parent=None):
super().__init__(parent)
self.options = options
self.arrow_img_path = arrow_img_path
self.is_expanded = False
# 主布局(标签 + 箭头)
self.main_layout = QHBoxLayout(self)
self.main_layout.setContentsMargins(0, 0, 0, 0)
self.main_layout.setSpacing(0)
self.main_layout.setAlignment(Qt.AlignLeft)
self.setFixedSize(63, 19) # 需要根据下拉框需要显示的文字来修改
# self.setFixedHeight(19)
# 1. 结果显示标签QLabel无clicked信号
self.result_label = QLabel(options[0])
self.result_label.setStyleSheet(
"""
background-image: url("");
color: #16ffff;
background-color: transparent;
border: none;
padding: 0px;
font-size: 18px;
"""
)
# self.result_label.setCursor(Qt.PointingHandCursor) # 手型光标提示可点击
self.main_layout.addWidget(
self.result_label, alignment=Qt.AlignVCenter | Qt.AlignLeft
)
# 2. 可点击的箭头标签QLabel
self.arrow_label = QLabel()
self.arrow_pixmap = QPixmap(arrow_img_path)
self.arrow_label.setStyleSheet("background-image: url(" ");")
self.arrow_label.setPixmap(
self.arrow_pixmap.scaled(12, 9, Qt.KeepAspectRatio, Qt.SmoothTransformation)
)
self.arrow_label.setCursor(Qt.PointingHandCursor)
self.main_layout.addWidget(self.arrow_label, alignment=Qt.AlignTop)
# 3. 下拉选项列表(默认选中第一个)
self.list_widget = QListWidget()
self.list_widget.setWindowFlags(Qt.Popup)
# 设置选项字体
font = QFont()
font.setPixelSize(16)
# 添加所有的下拉选项
for option in options:
item = QListWidgetItem(option)
item.setTextAlignment(Qt.AlignLeft)
item.setFont(font)
self.list_widget.addItem(item)
self.list_widget.setCurrentRow(0) # 默认选中第一项
self.list_widget.itemClicked.connect(self.select_option)
# 双保险监听:全局焦点变化 + 事件过滤
self.app = QApplication.instance()
self.app.focusChanged.connect(self.on_focus_changed)
self.list_widget.installEventFilter(self)
def mousePressEvent(self, event):
"""重写鼠标点击事件实现QLabel点击功能"""
# 判断点击是否在result_label或arrow_label区域内
# if self.result_label.underMouse() or self.arrow_label.underMouse():
# self.toggle_expand()
if self.arrow_label.underMouse():
self.toggle_expand()
super().mousePressEvent(event) # 传递事件,不影响其他组件
def toggle_expand(self):
"""切换下拉框展开/收起 + 箭头旋转"""
if self.is_expanded:
self.list_widget.hide()
# 箭头恢复向下
self.arrow_label.setPixmap(self.arrow_pixmap)
else:
# 计算下拉框位置(在标签下方对齐)
label_pos = self.result_label.mapToGlobal(
QPoint(0, self.result_label.height())
)
self.list_widget.setGeometry(
label_pos.x(), label_pos.y(), self.result_label.width() + 10, 80
)
self.list_widget.show()
self.list_widget.setFocus()
# 箭头旋转180度向上
transform = QTransform().rotate(180)
rotated_pixmap = self.arrow_pixmap.transformed(
transform, Qt.SmoothTransformation
)
self.arrow_label.setPixmap(rotated_pixmap)
self.is_expanded = not self.is_expanded
def select_option(self, item):
"""选择选项后更新标签 + 收起下拉框"""
self.result_label.setText(item.text())
self.list_widget.hide()
self.arrow_label.setPixmap(self.arrow_pixmap)
self.is_expanded = False
def on_focus_changed(self, old_widget, new_widget):
"""焦点变化时关闭下拉框"""
if self.is_expanded:
is_focus_on_self = (
new_widget == self
or new_widget == self.result_label
or new_widget == self.arrow_label
or (self.list_widget.isAncestorOf(new_widget) if new_widget else False)
)
if not is_focus_on_self:
self.list_widget.hide()
self.arrow_label.setPixmap(self.arrow_pixmap)
self.is_expanded = False
def eventFilter(self, obj, event):
"""点击外部关闭下拉框"""
if obj == self.list_widget and event.type() == QEvent.MouseButtonPress:
self.list_widget.hide()
self.arrow_label.setPixmap(
self.arrow_pixmap.scaled(
12, 9, Qt.KeepAspectRatio, Qt.SmoothTransformation
)
)
self.is_expanded = False
return True
return super().eventFilter(obj, event)
def setFont(self, font):
"""设置字体"""
self.result_label.setFont(font)
for i in range(self.list_widget.count()):
self.list_widget.item(i).setFont(font)
# 获取当前选中的设备名
def get_selected_device(self):
return self.result_label.text()
class SystemDiagnosticsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setWindowOpacity(0.0)
self._init_ui()
self.init_animations()
def _init_ui(self):
# 无边框模式
self.setWindowFlags(Qt.FramelessWindowHint)
# 加载系统诊断弹窗的背景图片
self.bg_pixmap = QPixmap(ImagePaths.SYSTEM_DIAGNOSTICS_POPUP_BG)
if self.bg_pixmap.isNull():
print("错误: 系统诊断弹窗背景图加载失败!请检查路径是否正确")
else:
# 窗口尺寸与图片尺寸完全一致
self.setFixedSize(self.bg_pixmap.size())
# 网格布局8行4列小框
grid_layout = QGridLayout(self)
grid_layout.setContentsMargins(24, 28, 20, 24)
# 图片路径(替换为实际路径)
box_image_path = ImagePaths.SYSTEM_DIAGNOSTICS_BOX
circle_normal_path = ImagePaths.SYSTEM_DIAGNOSTICS_STATUS_GREEN # 正常状态
circle_warning_path = ImagePaths.SYSTEM_DIAGNOSTICS_STATUS_YELLOW # 警告状态
circle_error_path = ImagePaths.SYSTEM_DIAGNOSTICS_STATUS_RED # 异常状态
ms_box_path = ImagePaths.SYSTEM_DIAGNOSTICS_MS_BG
dropdown_arrow_path = ImagePaths.SYSTEM_DIAGNOSTICS_DROPDOWN_ARROW
# 字体设置
ms_font = QFont()
ms_font.setPixelSize(14)
ms_color = QColor("#14abea")
# 生成小框
for row in range(8):
for col in range(4):
box_container = QWidget()
box_container.setObjectName(f"box_{row}_{col}")
box_container.setStyleSheet(
f"""
background-image: url("{box_image_path}");
background-repeat: no-repeat;
"""
)
box_layout = QHBoxLayout(box_container)
box_layout.setSpacing(0)
# ========== 状态圆圈(支持状态切换) ==========
circle_label = QLabel()
circle_label.status = "normal"
circle_label.pixmaps = {
"normal": QPixmap(circle_normal_path),
"warning": QPixmap(circle_warning_path),
"error": QPixmap(circle_error_path),
}
circle_label.setPixmap(circle_label.pixmaps["normal"])
circle_label.setStyleSheet("background: none;")
# ========== 自定义下拉框(支持获取设备名) ==========
led_dropdown = CustomDropdown(
options=["LED1", "LED2", "LED3"], arrow_img_path=dropdown_arrow_path
)
# ========== 秒数输入框(获取毫秒值) ==========
ms_container = QWidget()
ms_layout = QHBoxLayout(ms_container)
ms_layout.setContentsMargins(6, 0, 0, 0)
ms_edit = QLineEdit("5ms")
ms_edit.setFont(ms_font)
ms_edit.setStyleSheet(
f"""
background: none;
color: {ms_color.name()};
border: none;
outline: none;
background-color: transparent;
"""
)
ms_container.setStyleSheet(
f"""
background-image: url("{ms_box_path}");
background-repeat: no-repeat;
"""
)
ms_layout.addWidget(ms_edit)
# 保存组件引用 (动态增加)
box_container.circle = circle_label
box_container.dropdown = led_dropdown
box_container.ms_edit = ms_edit
# 间距调整
spacer1 = QSpacerItem(5, 1, QSizePolicy.Fixed, QSizePolicy.Minimum)
spacer2 = QSpacerItem(5, 1, QSizePolicy.Fixed, QSizePolicy.Minimum)
spacer3 = QSpacerItem(8, 1, QSizePolicy.Fixed, QSizePolicy.Minimum)
# box_layout.addItem(spacer1)
box_layout.addWidget(circle_label)
box_layout.addItem(spacer2)
box_layout.addWidget(led_dropdown)
# box_layout.addItem(spacer3)
box_layout.addWidget(ms_container)
grid_layout.addWidget(box_container, row, col)
def init_animations(self):
"""初始化显示动画:从下方滑入 + 淡入"""
# 1. 透明度动画从0→1与系统中心一致但时长不同
self.opacity_anim = QPropertyAnimation(self, b"windowOpacity")
self.opacity_anim.setDuration(400)
self.opacity_anim.setStartValue(0.0)
self.opacity_anim.setEndValue(1.0)
self.opacity_anim.setEasingCurve(QEasingCurve.OutCubic) # 缓动曲线不同
# 2. 位置动画从下方100px滑入目标位置核心差异点
self.pos_anim = QPropertyAnimation(self, b"geometry")
self.pos_anim.setDuration(400)
self.pos_anim.setEasingCurve(QEasingCurve.OutQuart) # 滑入效果更自然
# 3. 组合动画(同时执行滑入和淡入)
self.anim_group = QParallelAnimationGroup(self)
self.anim_group.addAnimation(self.opacity_anim)
self.anim_group.addAnimation(self.pos_anim)
def showEvent(self, event):
super().showEvent(event) # 先调用父类方法
# 动态计算动画起点在当前位置下方100px保持宽度和高度不变
current_geometry = self.geometry() # 当前位置和尺寸需提前用move设置
# 起点y坐标增加100px从下方滑入x和尺寸不变
start_rect = QRect(
current_geometry.x(),
current_geometry.y() + 100, # 下方100px
current_geometry.width(),
current_geometry.height()
)
# 设置动画起点和终点
self.pos_anim.setStartValue(start_rect)
self.pos_anim.setEndValue(current_geometry) # 终点:目标位置
# 启动动画
self.anim_group.start()
def paintEvent(self, event):
"""重写绘制事件,手动在透明背景上绘制图片"""
if not self.bg_pixmap.isNull():
painter = QPainter(self)
# 绘制背景图(完全覆盖窗口,无间隙)
painter.drawPixmap(self.rect(), self.bg_pixmap)
# 必须调用父类方法,确保子控件正常绘制
super().paintEvent(event)
"""
注意: row表示行号、col表示列号。都是从 0开始, 比如: 0行0列
"""
# ========== 对外接口:设置设备状态 ==========
def set_circle_status(self, row, col, status):
"""设置指定行列的状态(绿-黄-红) (normal/warning/error)"""
box = self.findChild(QWidget, f"box_{row}_{col}")
if box and hasattr(box, "circle"):
box.circle.setPixmap(box.circle.pixmaps[status])
box.circle.status = status
# ========== 对外接口:获取选中的设备名 ==========
def get_selected_device(self, row, col):
"""获取指定行列的选中设备名"""
box = self.findChild(QWidget, f"box_{row}_{col}")
if box and hasattr(box, "dropdown"):
return box.dropdown.get_selected_device()
return None
# ========== 对外接口:获取毫秒值 ==========
def get_ms_value(self, row, col):
"""获取指定行列的毫秒值如“5ms”"""
box = self.findChild(QWidget, f"box_{row}_{col}")
if box and hasattr(box, "ms_edit"):
# return box.ms_edit.text()
text = box.ms_edit.text().strip()
# 用正则提取数字(支持整数/小数,如"5"、"3.8"、"10.2ms"
import re
number_match = re.search(r"(\d+(?:\.\d+)?)", text)
if number_match:
return number_match.group(1)
return None
if __name__ == "__main__":
app = QApplication(sys.argv)
dialog = SystemDiagnosticsDialog()
dialog.show()
# 1. 设置0行0列的状态为“警告”状态
dialog.set_circle_status(0, 0, "warning")
# 2. 获取1行2列的选中设备名
device = dialog.get_selected_device(1, 2)
print(f"选中设备:{device}")
# 3. 获取3行1列的毫秒值
ms = dialog.get_ms_value(3, 1)
print(f"毫秒值:{ms}")
sys.exit(app.exec())

View File

@ -1,14 +1,19 @@
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QMessageBox, QApplication)
from PySide6.QtCore import Qt
from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QPainter, QPixmap, QFont
import sys
import resources.resources_rc
from utils.image_paths import ImagePaths
# 任务控件,如:管片任务、派单任务
"""
任务控件,如:管片任务、派单任务
"""
class TaskWidget(QWidget):
# 任务详情信号: task1表示第一条任务
task_details_signal = Signal(str)
def __init__(self, taskTitle:str, parent=None):
super().__init__(parent)
# 设置Widget大小与背景图一致
@ -153,8 +158,12 @@ class TaskWidget(QWidget):
def _show_detail_dialog(self, task_name):
"""显示任务详情弹窗"""
QMessageBox.information(self, "任务详情", f"任务 {task_name} 的详细信息...")
# QMessageBox.information(self, "任务详情", f"任务 {task_name} 的详细信息...")
"""
task1 表示第一条任务, 依次类推
"""
# 发送任务详情信号
self.task_details_signal.emit(task_name)
# --------------------------
# 对外接口:修改任务属性