add main_ui.py(中交三航主界面)

This commit is contained in:
2025-10-18 18:29:40 +08:00
parent 2c16ef2a7e
commit dc038b0c0d
31 changed files with 7601 additions and 0 deletions

View File

@ -0,0 +1,239 @@
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QWidget, QVBoxLayout, QSizePolicy
from PySide6.QtGui import QPixmap
from PySide6.QtCore import Qt, QPropertyAnimation, Property
import os
import resources.resource_rc
# 拱形进度条
class OverlapArcProgress(QWidget):
def __init__(self, bg_img_path, fg_img_path, parent=None):
super().__init__(parent)
self._progress = 0 # 进度0-100
self.setStyleSheet("background: transparent; border: none;")
# 加载并统一图片尺寸
self.bg_pixmap = QPixmap(bg_img_path)
self.fg_pixmap = QPixmap(fg_img_path)
if self.bg_pixmap.isNull():
print(f"错误:拱进度条背景图 {bg_img_path} 加载失败")
if self.fg_pixmap.isNull():
print(f"错误:拱进度条前景图 {fg_img_path} 加载失败")
if self.bg_pixmap.size() != self.fg_pixmap.size():
self.fg_pixmap = self.fg_pixmap.scaled(
self.bg_pixmap.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation
)
# 控件尺寸与图片一致(避免缩放失真)
self.setFixedSize(self.bg_pixmap.size())
self.total_height = self.bg_pixmap.height() # 缓存总高度(背景/前景高度一致)
# 背景标签(下层,完整显示背景)
self.bg_label = QLabel(self)
self.bg_label.setPixmap(self.bg_pixmap)
self.bg_label.setScaledContents(False) # 禁用缩放
# 前景标签(上层,初始加载完整前景图,通过高度控制显示区域)
self.fg_label = QLabel(self)
self.fg_label.setPixmap(self.fg_pixmap) # 直接加载完整前景图
self.fg_label.setScaledContents(False) # 禁用缩放(避免图片拉伸)
self.fg_label.setAlignment(Qt.AlignBottom) # 图片在标签内底部对齐
self.fg_label.raise_() # 确保在背景上层
# 初始状态高度为0位置在底部不显示任何内容
self.fg_label.setGeometry(0, self.total_height, self.bg_pixmap.width(), 0)
# ---------- 进度显示标签 ----------
self.progress_label = QLabel(self)
self.progress_label.setGeometry(217, 17, 121, 34)
self.progress_label.setStyleSheet("""
background-color: #444750;
color: #A2EF4D;
font-size: 20px;
font-weight: bold;
""")
# 文本在标签内部居中显示
self.progress_label.setAlignment(Qt.AlignCenter)
self.progress_label.raise_() # 确保在前景上层(最上层)
self.progress_label.setText("0%") # 初始文本
# ---------- 进度属性 ----------
@Property(int)
def progress(self):
return self._progress
@progress.setter
def progress(self, value):
if 0 <= value <= 100 and value != self._progress:
self._progress = value
self.update_foreground()
# 标签高度调整方式实现
def update_foreground(self):
"""通过调整前景标签的高度和Y坐标, 实现从底部向上填充的进度条"""
# 同步更新进度标签文本
self.progress_label.setText(f"{self._progress}%")
# print(f"{self._progress}%")
progress_ratio = self._progress / 100.0
k = 8 # 调整此系数k越大“快慢差异”越明显推荐范围 3~10
# 公式height_ratio = x + k*(x³/3 - x²/2 + x/6)
height_ratio = progress_ratio + k * (
(progress_ratio ** 3) / 3 -
(progress_ratio ** 2) / 2 +
progress_ratio / 6
)
height_ratio = max(0.0, min(1.0, height_ratio)) # 限制范围0-1
# 计算目标高度(随进度变化)
target_height = int(self.total_height * height_ratio)
# fg_label标签Y坐标 = 总高度 - 目标高度(实现底部对齐,向上延伸)
target_y = self.total_height - target_height
# 标签宽度与背景一致,高度为目标高度
self.fg_label.setGeometry(
0, # X坐标与背景左对齐
target_y, # Y坐标底部对齐
self.bg_pixmap.width(), # 宽度(与背景一致)
target_height # 高度(随进度变化)
)
class ArcProgressWidget(QWidget):
def __init__(self):
super().__init__()
# 加载MainWidget背景图
# 添加了 拱3.png 作为背景图
bg_img = ":/icons/images/拱3.png" # 需要修改为实际的图片的路径
# self.setStyleSheet(f"background-image: url({bg_img}); background-repeat: no-repeat; background-position: center;")
self.bg_pixmap = QPixmap(bg_img)
# # 设置控件widget大小与背景图一致
self.setFixedSize(self.bg_pixmap.size())
self.setSizePolicy(
QSizePolicy.Fixed, # 水平方向固定
QSizePolicy.Fixed # 垂直方向固定
)
# 创建背景标签
background_label = QLabel(self)
background_label.setPixmap(self.bg_pixmap)
background_label.setScaledContents(True)
background_label.setFixedSize(self.bg_pixmap.size())
# 创建拱形进度条控件
arc_bg_img = ":/icons/images/拱2.png" # 拱进度条背景(空心) # 需要修改为实际的图片的路径
arc_fg_img = ":/icons/images/拱1.png" # 拱进度条前景(实心) # 需要修改为实际的图片的路径
self.arc_progress = OverlapArcProgress(arc_bg_img, arc_fg_img, self)
# 显示重量的标签,单位是 kg
self.weight_label = QLabel(self)
self.weight_label.setGeometry(217, 118, 118, 29)
self.weight_label.setStyleSheet("""
background-color: #3a2528;
color: #A2EF4D;
font-size: 20px;
""")
# 文本在标签内部居中显示
self.weight_label.setAlignment(Qt.AlignCenter)
self.weight_label.setText("2000kg") # 初始文本
# 显示管片型号的标签 如 B1
self.segment_model_label = QLabel(self)
self.segment_model_label.setGeometry(98, 150, 116, 29)
self.segment_model_label.setStyleSheet("""
background-color: #3a2528;
color: #A2EF4D;
font-size: 20px;
""")
# 文本在标签内部居中显示
self.segment_model_label.setAlignment(Qt.AlignCenter)
self.segment_model_label.setText("B1") # 初始文本
# 显示频率的标签,单位是 Hz
self.frequency_label = QLabel(self)
self.frequency_label.setGeometry(217, 150, 118, 29)
self.frequency_label.setStyleSheet("""
background-color: #3a2528;
color: #A2EF4D;
font-size: 20px;
""")
# 文本在标签内部居中显示
self.frequency_label.setAlignment(Qt.AlignCenter)
self.frequency_label.setText("230Hz") # 初始文本
# 显示管片编号的标签 如 SHRB1-3
self.segment_number_label = QLabel(self)
self.segment_number_label.setGeometry(338, 150, 116, 29)
self.segment_number_label.setStyleSheet("""
background-color: #3a2528;
color: #A2EF4D;
font-size: 20px;
""")
# 文本在标签内部居中显示
self.segment_number_label.setAlignment(Qt.AlignCenter)
self.segment_number_label.setText("SHRB1-3") # 初始文本
# 显示状态的标签 (如:振动中等)
self.state_label = QLabel(self)
self.state_label.setGeometry(217, 182, 118, 29)
self.state_label.setStyleSheet("""
background-color: #3a2528;
color: #A2EF4D;
font-size: 20px;
""")
# 文本在标签内部居中显示
self.state_label.setAlignment(Qt.AlignCenter)
self.state_label.setText("振动中") # 初始文本
# 进度条测试
def testProgress(self, seconds: float):
# 启动动画从0%到100% (模拟进度加载)
self.animation = QPropertyAnimation(self.arc_progress, b"progress")
self.animation.setDuration(seconds * 1000)
self.animation.setStartValue(0)
self.animation.setEndValue(100)
self.animation.start()
# 进度条设置
def setProgress(self, progress:int):
"""
设置progress之后, 会根据该值调整进度条
Args:
progress: 传入去掉百分号之后的数值, 如80%, 传入80
"""
self.arc_progress.progress = progress
# 重量设置 (单位kg)
def setWeight(self, weight:float):
self.weight_label.setText(f"{weight}kg")
# 频率设置单位Hz
def setFrequency(self, frequency:float):
self.frequency_label.setText(f"{frequency}Hz")
# 状态设置 (...中)
def setState(self, stateStr:str):
self.state_label.setText(stateStr)
# 管片类型设置 (B1、B2等)
def setSegmentModel(self, segmentModelStr:str):
self.segment_model_label.setText(segmentModelStr)
# 管片编号设置
def setSegmentNumber(self, segmentNumberStr:str):
self.segment_number_label.setText(segmentNumberStr)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
window = ArcProgressWidget()
window.testProgress(120)
window.setState("测试中")
window.show()
sys.exit(app.exec())

View File

@ -0,0 +1,36 @@
from PySide6.QtWidgets import QPushButton
from PySide6.QtGui import QPainter, QColor, QFont, Qt, QBrush
class CircularButton(QPushButton):
"""自定义圆形按钮,处理 hover 颜色变化"""
def __init__(self, text, parent=None):
super().__init__(text, parent)
self.setFixedSize(40, 40)
# self.normal_color = QColor(1, 18, 98) # 正常颜色 (RGB: 1,18,98)
self.normal_color = QColor(86, 119, 252)
self.hover_color = QColor(24, 253, 255) # hover 颜色 (RGB: 24,253,255)
self.current_color = self.normal_color
self.setStyleSheet("background: transparent; border: none;")
def enterEvent(self, event):
self.current_color = self.hover_color
self.update()
super().enterEvent(event)
def leaveEvent(self, event):
self.current_color = self.normal_color
self.update()
super().leaveEvent(event)
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
# 绘制圆形背景
painter.setBrush(QBrush(self.current_color))
painter.setPen(Qt.NoPen)
painter.drawEllipse(0, 0, self.width(), self.height())
# 绘制白色文字
painter.setPen(Qt.white)
painter.setFont(QFont("Arial", 10, QFont.Bold))
painter.drawText(self.rect(), Qt.AlignCenter, self.text())

View File

@ -0,0 +1,407 @@
from PySide6.QtWidgets import (QApplication, QWidget, QPushButton, QLabel,
QVBoxLayout, QHBoxLayout)
from PySide6.QtGui import QPainter, QColor, QFont, QPixmap, Qt, QBrush
from PySide6.QtCore import QPoint, Signal, Slot
import sys
# 圆形按钮
from .circular_button import CircularButton
import resources.resource_rc
class HopperWidget(QWidget):
# 上料斗破拱信号
# 开启破拱 True关闭破拱 False
upper_arch_breaking_signal = Signal(bool)
# 下料斗破拱信号
# 开启破拱 True关闭破拱 False
lower_arch_breaking_signal = Signal(bool)
def __init__(self):
super().__init__()
self.setWindowTitle("料斗控制界面")
# 破拱状态 (True/False)
self.upper_arch_breaking_status = False # 初始为不破拱状态
self.lower_arch_breaking_status = False # 初始为不破拱状态
# 料斗控制界面的固定大小为 332x482
# 需要根据具体的料斗的图片来调整
self.setFixedSize(356, 496)
# 创建上位和下位料斗
self.upper_hopper = self.create_upper_hopper()
self.lower_hopper = self.create_lower_hopper()
# 主布局
main_layout = QVBoxLayout(self)
main_layout.addWidget(self.upper_hopper)
main_layout.addWidget(self.lower_hopper)
main_layout.setSpacing(0)
main_layout.setContentsMargins(10, 10, 0, 10)
self.connectSignalToSlot()
def create_upper_hopper(self):
"""创建上位料斗Widget"""
group = QWidget()
# 上位的 料斗控件的固定尺寸 !!!!
# 注意: 这里需要根据具体的料斗图片来修改
group.setFixedSize(332, 202)
# group.setStyleSheet("background-color: green")
layout = QVBoxLayout(group)
layout.setSpacing(0)
layout.setContentsMargins(0, 0, 0, 0)
# 标题标签(上位)
self.upper_title_label = QLabel("上位料斗")
self.upper_title_label.setFixedSize(79, 17)
self.upper_title_label.setAlignment(Qt.AlignCenter)
self.upper_title_label.setStyleSheet("color: #2fd3f2; font-size: 14px; font-weight: bold;")
layout.addWidget(self.upper_title_label, alignment=Qt.AlignCenter)
# 加载外框图片
outer_img = ":/icons/images/上位料斗1.png"
outer_pixmap = QPixmap(outer_img)
if outer_pixmap.isNull():
print(f"警告:图片 {outer_img} 加载失败,请检查路径!")
return group
outer_width = outer_pixmap.width()
outer_height = outer_pixmap.height()
# 背景容器(上位)
self.upper_bg_widget = QWidget()
self.upper_bg_widget.setFixedSize(outer_width, outer_height)
self.upper_bg_widget.setStyleSheet(f"background-image: url({outer_img}); background-repeat: no-repeat; background-position: center;")
layout.addWidget(self.upper_bg_widget, alignment=Qt.AlignCenter)
# 内框图片(上位)
inner_img = ":/icons/images/上位料斗2.png"
inner_pixmap = QPixmap(inner_img)
if not inner_pixmap.isNull():
self.upper_inner_label = QLabel(self.upper_bg_widget)
self.upper_inner_label.setPixmap(inner_pixmap)
self.upper_inner_label.setFixedSize(inner_pixmap.width(), inner_pixmap.height())
self.upper_inner_label.move(14, 9)
# 状态图片(上位,绿色)
status_img = ":/icons/images/料斗状态绿.png"
status_pixmap = QPixmap(status_img)
if not status_pixmap.isNull():
status_pixmap = status_pixmap.scaled(22, 22, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.upper_status_label = QLabel(self.upper_bg_widget)
self.upper_status_label.setPixmap(status_pixmap)
self.upper_status_label.move(32, 18)
self.upper_status_label.setScaledContents(False)
self.upper_status_label.setStyleSheet("background: none;")
# 破拱图片(上位)
arch_img = ":/icons/images/破拱.png"
arch_pixmap = QPixmap(arch_img)
if not arch_pixmap.isNull():
arch_pixmap = arch_pixmap.scaled(24, 21, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.upper_arch_label = QLabel(self.upper_bg_widget)
self.upper_arch_label.setPixmap(arch_pixmap)
self.upper_arch_label.move(outer_width - 56, 18)
self.upper_arch_label.setStyleSheet("background: none;")
self.upper_arch_label.setHidden(True) # 初始,不破拱状态,隐藏
# 重量文字(上位)
self.upper_weight_label = QLabel("5000kg", self.upper_bg_widget)
self.upper_weight_label.setAlignment(Qt.AlignCenter)
self.upper_weight_label.setStyleSheet("background: none; background-color: #262c38; color: #79c053; font-size: 14px; font-weight: bold;")
self.upper_weight_label.setFixedSize(93, 22)
self.upper_weight_label.move(outer_width//2 - 46, outer_height//2 - 30)
# 额外文字(上位)
self.upper_extra_label = QLabel("2.0方(预估)", self.upper_bg_widget)
self.upper_extra_label.setAlignment(Qt.AlignCenter)
self.upper_extra_label.setStyleSheet("background: none; background-color: #262c38; color: #79c053; font-size: 14px; font-weight: bold;")
self.upper_extra_label.setFixedSize(93, 22)
self.upper_extra_label.move(outer_width//2 - 46, outer_height//2)
# 料斗夹具图片(上位)
# clamp_img = ":/icons/images/料斗夹具.png"
# clamp_pixmap = QPixmap(clamp_img)
# if not clamp_pixmap.isNull():
# self.upper_clamp_label = QLabel()
# self.upper_clamp_label.setPixmap(clamp_pixmap)
# self.upper_clamp_label.setFixedSize(clamp_pixmap.width(), clamp_pixmap.height())
# layout.addWidget(self.upper_clamp_label, alignment=Qt.AlignCenter)
# 料斗夹具图片上位固定152x60尺寸图片自适应缩放
# 注意:目前由于给出的 料斗夹具图片尺寸大了,只能够先缩放,后期图片对了,可以用上面的
clamp_img = ":/icons/images/料斗夹具.png"
clamp_pixmap = QPixmap(clamp_img)
if not clamp_pixmap.isNull():
self.upper_clamp_label = QLabel()
# 1. 固定QLabel尺寸为152x60
self.upper_clamp_label.setFixedSize(152, 60)
# 2. 图片缩放到152x60保持比例平滑缩放
scaled_pixmap = clamp_pixmap.scaled(
152, 60,
Qt.KeepAspectRatio, # 保持图片原始比例,避免变形
Qt.SmoothTransformation # 平滑缩放,提升显示效果
)
self.upper_clamp_label.setPixmap(scaled_pixmap)
# 3. 确保图片在QLabel中居中显示
self.upper_clamp_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.upper_clamp_label, alignment=Qt.AlignCenter)
# 按钮(上位)
self.upper_open_btn = CircularButton("", group)
self.upper_open_btn.move(60, 153) # 上位料斗的开按钮位置
self.upper_close_btn = CircularButton("", group)
self.upper_close_btn.move(233, 153) # 上位料斗的关按钮位置
self.upper_arch_btn = CircularButton("", group)
self.upper_arch_btn.move(290, 37) # 上位料斗的破拱按钮位置
return group
def connectSignalToSlot(self):
self.upper_arch_btn.clicked.connect(self.onUpperArchBreaking)
self.lower_arch_btn.clicked.connect(self.onLowerArchBreaking)
@Slot()
def onUpperArchBreaking(self):
if self.upper_arch_breaking_status == False: # 不破拱状态
# 此时,点击按钮为开启破拱
self.upper_arch_breaking_status = True
self.upper_arch_label.setHidden(False)
self.upper_arch_breaking_signal.emit(self.upper_arch_breaking_status)
else: # 破拱状态
# 此时,点击按钮为关闭破拱
self.upper_arch_breaking_status = False
self.upper_arch_label.setHidden(True)
self.upper_arch_breaking_signal.emit(self.upper_arch_breaking_status)
@Slot()
def onLowerArchBreaking(self):
if self.lower_arch_breaking_status == False: # 不破拱状态
# 此时,点击按钮为开启破拱
self.lower_arch_breaking_status = True
self.lower_arch_label.setHidden(False)
self.lower_arch_breaking_signal.emit(self.lower_arch_breaking_status)
else: # 破拱状态
# 此时,点击按钮为关闭破拱
self.lower_arch_breaking_status = False
self.lower_arch_label.setHidden(True)
self.upper_arch_breaking_signal.emit(self.lower_arch_breaking_status)
def create_lower_hopper(self):
"""创建下位料斗Widget"""
group = QWidget()
# 下位的 料斗控件的固定尺寸 !!!!
# 注意: 这里需要根据具体的料斗图片来修改
group.setFixedSize(332, 280)
# group.setStyleSheet("background-color: black")
layout = QVBoxLayout(group)
layout.setSpacing(0)
layout.setContentsMargins(0, 0, 0, 0)
# 标题标签(下位)
self.lower_title_label = QLabel("低位料斗")
self.lower_title_label.setFixedSize(79, 17)
self.lower_title_label.setAlignment(Qt.AlignCenter)
self.lower_title_label.setStyleSheet("color: #2fd3f2; font-size: 14px; font-weight: bold;")
layout.addWidget(self.lower_title_label, alignment=Qt.AlignCenter)
# 加载外框图片
outer_img = ":/icons/images/下位料斗1.png"
outer_pixmap = QPixmap(outer_img)
if outer_pixmap.isNull():
print(f"警告:图片 {outer_img} 加载失败,请检查路径!")
return group
outer_width = outer_pixmap.width()
outer_height = outer_pixmap.height()
# 背景容器(下位)
self.lower_bg_widget = QWidget()
self.lower_bg_widget.setFixedSize(outer_width, outer_height)
self.lower_bg_widget.setStyleSheet(f"background-image: url({outer_img}); background-repeat: no-repeat; background-position: center;")
layout.addWidget(self.lower_bg_widget, alignment=Qt.AlignCenter)
# 内框图片(下位)
inner_img = ":/icons/images/下位料斗2.png"
inner_pixmap = QPixmap(inner_img)
if not inner_pixmap.isNull():
self.lower_inner_label = QLabel(self.lower_bg_widget)
self.lower_inner_label.setPixmap(inner_pixmap)
self.lower_inner_label.setFixedSize(inner_pixmap.width(), inner_pixmap.height())
self.lower_inner_label.move(14, 9)
# 状态图片(下位,红色)
status_img = ":/icons/images/料斗状态绿.png"
status_pixmap = QPixmap(status_img)
if not status_pixmap.isNull():
status_pixmap = status_pixmap.scaled(22, 22, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.lower_status_label = QLabel(self.lower_bg_widget)
self.lower_status_label.setPixmap(status_pixmap)
self.lower_status_label.move(32, 18)
self.lower_status_label.setScaledContents(False)
self.lower_status_label.setStyleSheet("background: none;")
# 破拱图片(下位)
arch_img = ":/icons/images/破拱.png"
arch_pixmap = QPixmap(arch_img)
if not arch_pixmap.isNull():
arch_pixmap = arch_pixmap.scaled(24, 21, Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.lower_arch_label = QLabel(self.lower_bg_widget)
self.lower_arch_label.setPixmap(arch_pixmap)
self.lower_arch_label.move(outer_width - 56, 18)
self.lower_arch_label.setStyleSheet("background: none;")
self.lower_arch_label.setHidden(True) # 初始,不破拱状态,隐藏
# 重量文字(下位)
self.lower_weight_label = QLabel("5000kg", self.lower_bg_widget)
self.lower_weight_label.setStyleSheet("background: none; background-color: #262c38; color: #79c053; font-size: 14px;font-weight: bold;")
self.lower_weight_label.setAlignment(Qt.AlignCenter)
self.lower_weight_label.setFixedSize(93, 22)
self.lower_weight_label.move(outer_width//2 - 46, outer_height//2 - 46)
# 额外文字(下位)
self.lower_extra_label = QLabel("开: 25°", self.lower_bg_widget)
self.lower_extra_label.setAlignment(Qt.AlignCenter)
self.lower_extra_label.setStyleSheet("background: none; background-color: #262c38; color: #79c053; font-size: 14px;font-weight: bold;")
self.lower_extra_label.setFixedSize(93, 22)
self.lower_extra_label.move(outer_width//2 - 46, outer_height//2 - 20)
# 料斗夹具图片(下位)
# clamp_img = ":/icons/images/料斗夹具.png"
# clamp_pixmap = QPixmap(clamp_img)
# if not clamp_pixmap.isNull():
# self.lower_clamp_label = QLabel()
# self.lower_clamp_label.setPixmap(clamp_pixmap)
# self.lower_clamp_label.setFixedSize(clamp_pixmap.width(), clamp_pixmap.height())
# layout.addWidget(self.lower_clamp_label, alignment=Qt.AlignCenter)
# 目前由于给出的 料斗夹具图片尺寸大了,只能够先缩放,后期图片对了,可以用上面的
clamp_img = ":/icons/images/料斗夹具.png"
clamp_pixmap = QPixmap(clamp_img)
if not clamp_pixmap.isNull():
self.lower_clamp_label = QLabel()
# 1. 固定QLabel尺寸为152x60
self.lower_clamp_label.setFixedSize(152, 60)
# 2. 图片缩放到152x60保持比例平滑缩放
scaled_pixmap = clamp_pixmap.scaled(
152, 60,
Qt.KeepAspectRatio, # 保持图片原始比例,避免变形
Qt.SmoothTransformation # 平滑缩放,提升显示效果
)
self.lower_clamp_label.setPixmap(scaled_pixmap)
# 3. 确保图片在QLabel中居中显示
self.lower_clamp_label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.lower_clamp_label, alignment=Qt.AlignCenter)
# 按钮(下位)
self.lower_open_btn = CircularButton("", group)
self.lower_open_btn.move(60, 233) # 下位料斗的开按钮位置
self.lower_close_btn = CircularButton("", group)
self.lower_close_btn.move(233, 233) # 下位料斗的关按钮位置
self.lower_arch_btn = CircularButton("", group)
self.lower_arch_btn.move(290, 34) # 下位料斗的破拱按钮位置
return group
# 上料斗重量设置
def setUpperHopperWeight(self, weight:float):
self.upper_weight_label.setText(f"{weight}kg")
# 上料斗方量设置
def setUpperHopperVolume(self, volume: float):
"""Args:
volume : 传入多少方
"""
self.upper_extra_label.setText(f"{volume}方(预估)")
# 下料斗重量设置
def setLowerHopperWeight(self, weight:float):
self.lower_weight_label.setText(f"{weight}kg")
# 下料斗开合角度设置
def setLowerHopperOpeningAngle(self, angle: float):
"""Args:
angle : 传入多少度 (单位°)
"""
self.lower_extra_label.setText(f"开: {angle}°")
# ------------------------------
# 设置上料斗状态0=绿1=黄2=红)
# ------------------------------
def setUpperArchStatus(self, status: int):
"""
设置上料斗状态图片
Args:
status: 状态值0=绿1=黄2=红),非指定值不操作
"""
# 过滤无效状态不在0/1/2范围内则直接返回保持当前图片
if status not in [0, 1, 2]:
return
# 状态-图片路径映射
# 注意:需要替换为实际的图片路径
status_img_map = {
0: ":/icons/images/料斗状态绿.png",
1: ":/icons/images/料斗状态黄.png",
2: ":/icons/images/料斗状态红.png"
}
img_path = status_img_map[status]
# 加载并缩放图片
status_pixmap = QPixmap(img_path)
if not status_pixmap.isNull():
status_pixmap = status_pixmap.scaled(
22, 22,
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
self.upper_status_label.setPixmap(status_pixmap)
# ------------------------------
# 设置下料斗状态0=绿1=黄2=红)
# ------------------------------
def setLowerArchStatus(self, status: int):
"""
设置下料斗状态图片
Args:
status: 状态值0=绿1=黄2=红),非指定值不操作
"""
if status not in [0, 1, 2]:
return
# 注意:需要替换为实际的图片路径
status_img_map = {
0: ":/icons/images/料斗状态绿.png",
1: ":/icons/images/料斗状态黄.png",
2: ":/icons/images/料斗状态红.png"
}
img_path = status_img_map[status]
status_pixmap = QPixmap(img_path)
if not status_pixmap.isNull():
status_pixmap = status_pixmap.scaled(
22, 22,
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
self.lower_status_label.setPixmap(status_pixmap)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = HopperWidget()
window.setLowerHopperWeight(2000)
window.setUpperHopperVolume(3.0)
window.setLowerHopperOpeningAngle(45)
window.setUpperArchStatus(2)
window.setLowerArchStatus(1)
window.show()
sys.exit(app.exec())

View File

@ -0,0 +1,169 @@
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QHBoxLayout
from PySide6.QtGui import QPixmap,QRegion,QPainter, QColor, QPainterPath
from PySide6.QtCore import Qt, QPropertyAnimation, Property, QSize, QRectF
import sys
class MaskedLabel(QLabel):
def __init__(self, parent=None):
super().__init__(parent)
self.setFixedSize(28, 20) # 遮罩大小
self.setStyleSheet("background-color: transparent; border: none;")
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
parent_widget = self.parent()
bg_color = QColor("#f3f3f3") # 默认颜色
if parent_widget:
# 从父控件的调色板中提取背景色
bg_color = parent_widget.palette().color(parent_widget.backgroundRole())
# 用父控件背景色绘制遮罩
painter.setBrush(bg_color)
painter.setPen(Qt.NoPen)
# 步骤3绘制遮罩路径逻辑不变
path = QPainterPath()
mask_rect = QRectF(0, 0, 28, 20)
circle_radius = 10
circle_center_x = 28
circle_center_y = 10
path.addRect(mask_rect)
path.addEllipse(
circle_center_x - circle_radius,
circle_center_y - circle_radius,
circle_radius * 2,
circle_radius * 2
)
path.setFillRule(Qt.OddEvenFill)
painter.drawPath(path)
class LinearProductionProgress(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._progress = 0 # 进度0-100
self.setFixedSize(450 + 18, 20)
self.setStyleSheet(
"""
border-radius: 9px;
"""
)
# 底层背景
self.bg_label = QLabel(self)
self.bg_label.setFixedSize(450, 20)
self.bg_label.setStyleSheet(
"""
background-color: #e7e7e7;
"""
)
self.bg_label.move(18, 0)
# 上层进度填充
self.fg_label = QLabel(self)
self.fg_label.setStyleSheet(
"""
background-color: #0052d9;
min-width: 18px;
"""
)
self.fg_label.setFixedHeight(20)
self.fg_label.move(0, 0)
self.fg_label.raise_()
# 百分比标签宽33px高19px右偏9px
self.percent_label = QLabel(self)
self.percent_label.setText("0%")
self.percent_label.setAlignment(Qt.AlignCenter)
self.percent_label.setFixedSize(33, 19)
self.percent_label.setStyleSheet(
"""
color: white;
font-size: 12px;
font-weight: bold;
background-color: transparent;
"""
)
self.percent_label.move(18, 0) # 百分比标签初始位置
self.percent_label.raise_()
# 遮盖左侧区域
self.mask_label = MaskedLabel(self)
self.mask_label.move(0, 0)
# 进度属性
@Property(int)
def progress(self):
return self._progress
@progress.setter
def progress(self, value):
if 0 <= value <= 100 and value != self._progress:
self._progress = value
self.update_foreground()
# 线性更新进度显示
def update_foreground(self):
# 注意为了实现前4%的动态效果,需要从 fd_width的4%起开始计算
fg_width = int(450 * (self._progress + 4) / 100)
self.fg_label.setFixedWidth(fg_width)
# 计算百分比标签位置:进度条右边缘 - 9px偏移 - 标签宽度33px
if fg_width > 60: # 当上层进度条宽度大于60px开始移动
label_x = fg_width - 9 - 33
# 移动百分比标签
self.percent_label.move(label_x, 0)
else:
# 复原百分比标签
# 移动回初始位置
self.percent_label.move(18, 0)
# 设置百分比标签
self.percent_label.setText(f"{self._progress}%")
class ProductionProgressWidget(QWidget):
def __init__(self):
super().__init__()
self.setFixedSize(620, 49) # 进度条控件大小
# 左侧文字标签
self.text_label = QLabel("生产进度")
self.text_label.setFixedSize(112, 29)
self.text_label.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.text_label.setStyleSheet("font-family: 'Microsoft YaHei';font-size: 18px;")
# 右侧进度条
self.linear_progress = LinearProductionProgress()
self.main_layout = QHBoxLayout(self)
self.main_layout.addWidget(self.text_label)
self.main_layout.addWidget(self.linear_progress)
self.main_layout.setContentsMargins(0, 0, 0, 0)
def testProgress(self, seconds: float):
self.animation = QPropertyAnimation(self.linear_progress, b"progress")
self.animation.setDuration(seconds * 1000)
self.animation.setStartValue(0)
self.animation.setEndValue(100)
self.animation.start()
def setProgress(self, progress: float):
"""
设置progress之后, 会根据该值调整进度条
Args:
progress: 传入去掉百分号之后的数值, 如80%, 传入80.0
"""
self.linear_progress.progress = progress
if __name__ == "__main__":
app = QApplication(sys.argv)
window = ProductionProgressWidget()
# window.testProgress(60) # 进度条测试
window.show()
sys.exit(app.exec())

View File

@ -0,0 +1,241 @@
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QMessageBox, QLabel, QGridLayout, QDialog
from PySide6.QtGui import QPainter, QColor, QBrush, QFont, QPixmap
from PySide6.QtCore import Qt
import sys
import datetime
import resources.resource_rc
class DeviceStatusPopup(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("设备状态")
self.setFixedSize(898, 354) # 固定总尺寸为898x354
# 通用字体设置:微软雅黑 18px
font = QFont()
font.setFamily("Microsoft YaHei")
font.setPointSize(18)
# 初始化UI组件
self.ip_label = QLabel("ip地址")
self.ip_value = QLabel()
self.status_label = QLabel("连接状态")
self.status_img = QLabel()
self.status_img.setFixedSize(80, 41) # 状态图片尺寸
self.time_label = QLabel("更新时间")
self.time_value = QLabel()
self.algorithm_label = QLabel("算法结果")
self.algorithm_value = QLabel()
# 右侧大图片369x185
self.right_image = QLabel()
# self.right_image.setFixedSize(369, 185)
self.right_image.setFixedSize(520, 320)
# 临时设置占位图(实际使用时替换为你的图片路径)
# placeholder = QPixmap(520, 340)
# placeholder.fill(QColor(229, 229, 229)) # 灰色占位
# self.right_image.setPixmap(placeholder)
self.right_image.setText("图片未加载")
self.right_image.setStyleSheet("background-color: rgb(229,229,229)")
self.right_image.setAlignment(Qt.AlignCenter) # 图片居中显示
# 左侧栏字体设置:微软雅黑 18px
left_font = QFont("Microsoft YaHei", 18)
# 右侧栏字体设置:微软雅黑 15px
right_font = QFont("Microsoft YaHei", 15)
# 设置左侧文本标签的字体和左对齐
for label in [self.ip_label, self.status_label,
self.time_label, self.algorithm_label]:
label.setFont(left_font)
label.setAlignment(Qt.AlignLeft) # 左对齐
# 设置右侧文本标签的字体和左对齐
for label in [self.ip_value, self.time_value, self.algorithm_value]:
label.setFont(right_font)
label.setAlignment(Qt.AlignLeft) # 左对齐
# 三列布局:左侧标签列 + 中间内容列 + 右侧图片列
layout = QGridLayout()
# 设置布局边距和间距(避免内容贴边)
layout.setContentsMargins(10, 10, 10, 10) # 上下左右边距
layout.setSpacing(15) # 控件间距
# 第一列:标签(左对齐)
layout.addWidget(self.ip_label, 0, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
layout.addWidget(self.status_label, 1, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
layout.addWidget(self.time_label, 2, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
layout.addWidget(self.algorithm_label, 3, 0, alignment=Qt.AlignLeft | Qt.AlignVCenter)
# 第二列:内容(左对齐)
layout.addWidget(self.ip_value, 0, 1, alignment=Qt.AlignLeft | Qt.AlignVCenter)
layout.addWidget(self.status_img, 1, 1, alignment=Qt.AlignLeft | Qt.AlignVCenter)
layout.addWidget(self.time_value, 2, 1, alignment=Qt.AlignLeft | Qt.AlignVCenter)
layout.addWidget(self.algorithm_value, 3, 1, alignment=Qt.AlignLeft | Qt.AlignVCenter)
# 第三列右侧大图片跨4行垂直居中
layout.addWidget(self.right_image, 0, 2, 4, 1, alignment=Qt.AlignCenter)
# 列宽分配总宽度898 = 左列 + 中列 + 右列(369) + 边距(40) + 间距(30)
# 计算可用宽度898 - 10*2(边距) - 15*2(列间距) - 520(右列) = 328
layout.setColumnStretch(0, 1) # 左列占1份
layout.setColumnStretch(1, 2) # 中列占2份总3份分配328宽度
self.setLayout(layout)
# 初始状态设置
self.update_connection_status("在线")
self.update_algorithm_result("ok")
self.update_ip_address("192.168.1.5")
def update_connection_status(self, status: str):
"""更新连接状态,切换显示图片"""
img_path = ":/icons/images/在线.png" if status == "在线" else ":/icons/images/离线.png"
pixmap = QPixmap(img_path)
self.status_img.setPixmap(pixmap.scaled(
self.status_img.width(),
self.status_img.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
self._update_time()
def _update_time(self):
"""刷新当前时间"""
now = datetime.datetime.now()
self.time_value.setText(now.strftime("%Y-%m-%d %H:%M:%S"))
def update_ip_address(self, ip: str):
"""更新IP地址"""
self.ip_value.setText(ip)
def update_algorithm_result(self, result: str):
"""更新算法结果"""
self.algorithm_value.setText(result)
def update_right_image(self, img_path: str):
"""更新右侧大图片的接口"""
pixmap = QPixmap(img_path)
self.right_image.setPixmap(pixmap.scaled(
self.right_image.width(),
self.right_image.height(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
))
class CircularPushButton(QPushButton):
"""圆形按钮组件,点击触发弹窗"""
def __init__(self, text, parent=None):
super().__init__(text, parent)
self.setFixedSize(66, 66) # 固定尺寸60x60
self.setFont(QFont("Microsoft YaHei", 9)) # 字体适配尺寸
# 状态颜色定义
self.color_map = {
0: QColor(109, 218, 99), # 正常-绿色
1: QColor(222, 182, 61), # 警告-黄色
2: QColor(241, 63, 61) # 异常-红色
}
self.current_status = 0 # 默认正常状态
def set_status(self, status: int):
"""设置状态0-正常1-警告2-异常"""
if status in self.color_map:
self.current_status = status
self.update() # 触发重绘
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing) # 抗锯齿
# 绘制圆形背景
brush = QBrush(self.color_map[self.current_status])
painter.setBrush(brush)
painter.setPen(Qt.NoPen)
painter.drawEllipse(0, 0, self.width(), self.height())
# 绘制文字
painter.setPen(Qt.black)
painter.drawText(self.rect(), Qt.AlignCenter, self.text())
class StatusMonitorWidget(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.setWindowTitle("设备状态监控面板")
# self.resize(600, 120) # 窗口大小适配按钮尺寸
self.setFixedSize(720, 100)
layout = QHBoxLayout(self)
layout.setSpacing(15) # 组件间距
layout.setContentsMargins(20, 20, 20, 20) # 窗口边距
# 初始化所有圆形按钮
self.buttons = [
CircularPushButton("RFID对比"),
CircularPushButton("盖板对齐"),
CircularPushButton("溢料状态"),
CircularPushButton("下料度"),
CircularPushButton("IOT平台"),
CircularPushButton("任务平台"),
CircularPushButton("搅拌楼"),
CircularPushButton("LED"),
CircularPushButton("控制设备")
]
# 为每个按钮连接点击弹窗事件
for idx, btn in enumerate(self.buttons):
btn.clicked.connect(lambda checked, btn_idx=idx: self.show_popup(btn_idx))
# 将按钮添加到布局
for btn in self.buttons:
layout.addWidget(btn)
def show_popup(self, btn_idx):
"""点击按钮后弹出提示窗口"""
btn_text = self.buttons[btn_idx].text()
status_map = {0: "正常", 1: "警告", 2: "异常"}
status = status_map[self.buttons[btn_idx].current_status]
# QMessageBox.information(
# self,
# f"{btn_text} 详情",
# f"设备:{btn_text}\n状态{status}\n"
# )
statusWin = DeviceStatusPopup(self)
# 此处 可以更新设备的状态,如 设备ip、算法结果等
# 需要连接检测相关代码
statusWin.exec()
def setButtonStatus(self, button_name: str, status: int):
"""
根据按钮名称设置状态
Args:
button_name: 按钮显示的文本(如"RFID对比"
status: 状态值0-正常1-警告2-异常)
"""
# 遍历按钮列表,匹配名称
for btn in self.buttons:
if btn.text() == button_name:
btn.set_status(status) # 调用按钮的set_status方法
return # 找到后退出循环
# 未找到对应按钮,打印提示
print(f"警告:未找到名为「{button_name}」的按钮")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = StatusMonitorWidget()
window.setButtonStatus("溢料状态", 1)
window.setButtonStatus("LED", 2)
window.show()
sys.exit(app.exec())

View File

@ -0,0 +1,76 @@
from PySide6.QtWidgets import QApplication, QPushButton, QHBoxLayout, QWidget
from PySide6.QtGui import QIcon
from PySide6.QtCore import QSize, Qt
import sys
import resources.resource_rc
class SystemButtonWidget(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# 创建水平布局并设置按钮间距为27px
layout = QHBoxLayout()
layout.setSpacing(27) # 按钮之间的间距
layout.setContentsMargins(6, 6, 6, 6) # 布局边缘留白
self.setWindowTitle("系统按钮")
self.setFixedSize(776, 65) # widget大小
# 按钮样式表
button_style = """
QPushButton {
width: 120px; /* 宽120px */
height: 40px; /* 高40px */
background-color: #7f95bf; /* 平时颜色 */
color: white; /* 文字颜色(白色更清晰) */
font-size: 20px; /* 字号20px */
border: none; /* 去掉默认边框 */
border-radius: 4px; /* 轻微圆角(可选,更美观) */
}
QPushButton:hover {
background-color: #2573d3; /* 鼠标悬停颜色 */
}
QPushButton:pressed {
background-color: #1d5cb8; /* 点击时颜色(可选增强) */
}
"""
# 创建按钮列表(图标+文字)
# 注意:需要替换为实际的图片路径
buttons_info = [
("系统启动", ":/icons/images/系统启动.png"),
("系统停止", ":/icons/images/系统停止.png"),
("系统复位", ":/icons/images/系统复位.png"),
("AI检测", ":/icons/images/AI检测.png"),
("任务刷新", ":/icons/images/任务刷新.png")
]
# 存放按钮控件的字典
self.buttons = dict()
for text, icon_path in buttons_info:
btn = QPushButton(text)
# 设置图标
icon = QIcon(icon_path)
btn.setIcon(icon)
btn.setIconSize(QSize(24, 24)) # 图标尺寸(与按钮比例协调)
# 设置固定尺寸(确保宽高生效)
btn.setFixedSize(120, 40)
# 应用样式
btn.setStyleSheet(button_style)
# 添加到布局
layout.addWidget(btn)
self.buttons[text] = btn
self.setLayout(layout)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SystemButtonWidget()
window.show()
sys.exit(app.exec())