feeding 下料
4
.gitignore
vendored
@ -33,3 +33,7 @@ PyQt_Fluent_Widgets.egg-info/
|
|||||||
PySide6_Fluent_Widgets.egg-info/
|
PySide6_Fluent_Widgets.egg-info/
|
||||||
PyQt6_Fluent_Widgets.egg-info/
|
PyQt6_Fluent_Widgets.egg-info/
|
||||||
PySide2_Fluent_Widgets.egg-info/
|
PySide2_Fluent_Widgets.egg-info/
|
||||||
|
/hardware/__pycache__
|
||||||
|
__pycache__
|
||||||
|
/core/__pycache__
|
||||||
|
/vision/__pycache__
|
||||||
|
|||||||
@ -1,21 +1,21 @@
|
|||||||
# camera_config.ini
|
# camera_config.ini
|
||||||
# 相关的摄像头的配置文件
|
# 相关的摄像头的配置文件
|
||||||
[上位料斗]
|
[上位料斗]
|
||||||
ip = 192.168.1.50
|
ip = 192.168.250.60
|
||||||
port = 554
|
port = 554
|
||||||
username = admin
|
username = admin
|
||||||
password = XJ123456
|
password = XJ123456
|
||||||
channel = 101
|
channel = 101
|
||||||
|
|
||||||
[下位料斗]
|
[下位料斗]
|
||||||
ip = 192.168.1.51
|
ip = 192.168.250.61
|
||||||
port = 554
|
port = 554
|
||||||
username = admin
|
username = admin
|
||||||
password = XJ123456
|
password = XJ123456
|
||||||
channel = 101
|
channel = 101
|
||||||
|
|
||||||
[模具车]
|
[模具车]
|
||||||
ip = 192.168.1.51
|
ip = 192.168.250.61
|
||||||
port = 554
|
port = 554
|
||||||
username = admin
|
username = admin
|
||||||
password = XJ123456
|
password = XJ123456
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
from PySide6.QtCore import Qt, QPropertyAnimation, QRect, QParallelAnimationGroup, QEasingCurve
|
from PySide6.QtCore import Qt, QPropertyAnimation, QRect, QParallelAnimationGroup, QEasingCurve
|
||||||
from view.widgets.system_center_dialog import SystemCenterDialog
|
from view.widgets.system_center_dialog import SystemCenterDialog
|
||||||
from view.widgets.bottom_control_widget import BottomControlWidget
|
from view.widgets.bottom_control_widget import BottomControlWidget
|
||||||
|
from view.widgets.system_diagnostics_dialog import SystemDiagnosticsDialog
|
||||||
|
|
||||||
"""
|
"""
|
||||||
控制主界面底部的所有按钮, 包括系统诊断、系统中心等的行为。
|
控制主界面底部的所有按钮, 包括系统诊断、系统中心等的行为。
|
||||||
@ -19,12 +20,20 @@ class BottomControlController:
|
|||||||
self.system_center_dialog.hide() # 初始隐藏 (必须)
|
self.system_center_dialog.hide() # 初始隐藏 (必须)
|
||||||
self._init_system_center_dialog_hide_animations()
|
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()
|
self._bind_dialog_signals()
|
||||||
|
|
||||||
def _bind_buttons(self):
|
def _bind_buttons(self):
|
||||||
# 底部系统中心按钮 → 触发弹窗显示/隐藏
|
# 底部系统中心按钮 → 触发弹窗显示/隐藏
|
||||||
self.bottom_control_widget.center_btn.clicked.connect(self.toggle_system_center_dialog)
|
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):
|
def _bind_dialog_signals(self):
|
||||||
"""绑定弹窗按钮的信号"""
|
"""绑定弹窗按钮的信号"""
|
||||||
self.system_center_dialog.sys_setting_clicked.connect(self.handle_sys_setting)
|
self.system_center_dialog.sys_setting_clicked.connect(self.handle_sys_setting)
|
||||||
@ -53,7 +62,7 @@ class BottomControlController:
|
|||||||
self.hide_anim_group.finished.connect(self.system_center_dialog.hide)
|
self.hide_anim_group.finished.connect(self.system_center_dialog.hide)
|
||||||
|
|
||||||
def toggle_system_center_dialog(self):
|
def toggle_system_center_dialog(self):
|
||||||
"""切换弹窗的显示/隐藏状态"""
|
"""切换系统中心弹窗的显示/隐藏状态"""
|
||||||
if self.system_center_dialog.isVisible():
|
if self.system_center_dialog.isVisible():
|
||||||
# 已显示 → 隐藏
|
# 已显示 → 隐藏
|
||||||
# self.system_center_dialog.hide()
|
# self.system_center_dialog.hide()
|
||||||
@ -94,7 +103,7 @@ class BottomControlController:
|
|||||||
# 设置弹窗位置
|
# 设置弹窗位置
|
||||||
self.system_center_dialog.move(dialog_x, dialog_y)
|
self.system_center_dialog.move(dialog_x, dialog_y)
|
||||||
|
|
||||||
# ------------------- 业务逻辑方法-------------------
|
# ------------------- 系统中心弹窗业务逻辑-------------------
|
||||||
def handle_sys_setting(self):
|
def handle_sys_setting(self):
|
||||||
"""系统设置按钮的业务逻辑"""
|
"""系统设置按钮的业务逻辑"""
|
||||||
# print("执行系统设置逻辑:如打开系统配置窗口、修改参数等")
|
# print("执行系统设置逻辑:如打开系统配置窗口、修改参数等")
|
||||||
@ -109,3 +118,77 @@ class BottomControlController:
|
|||||||
def handle_user_center(self):
|
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()
|
||||||
153
controller/hopper_controller.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
from PySide6.QtCore import QTimer, Signal, QObject, Slot
|
||||||
|
import threading
|
||||||
|
from hardware.transmitter import TransmitterController
|
||||||
|
from hardware.relay import RelayController
|
||||||
|
from view.widgets.hopper_widget import HopperWidget
|
||||||
|
from view.widgets.conveyor_system_widget import ConveyorSystemWidget
|
||||||
|
|
||||||
|
# 信号类:后台线程向主线程传递数据
|
||||||
|
class HopperSignals(QObject):
|
||||||
|
upper_weight_updated = Signal(int) # 上料斗重量更新信号
|
||||||
|
|
||||||
|
class HopperController:
|
||||||
|
def __init__(self, hopper_view:HopperWidget, conveyor_view:ConveyorSystemWidget):
|
||||||
|
self.hopper_view = hopper_view
|
||||||
|
self.conveyor_view = conveyor_view # 控制传送带中的上料斗
|
||||||
|
|
||||||
|
self.signals = HopperSignals() # 信号
|
||||||
|
|
||||||
|
# 下料斗夹爪测试用例数据
|
||||||
|
# 注意:目前只控制 下料斗的夹爪角度变化
|
||||||
|
self.angle = 10 # 夹爪当前角度
|
||||||
|
self.max_angle = 60 # 夹爪最大张开角度
|
||||||
|
self.min_angle = 10 # 夹爪最小张开角度
|
||||||
|
self.is_add = True # 角度增加/减小 控制
|
||||||
|
self.timer_angle = QTimer() # 角度更新定时器
|
||||||
|
self.timer_angle.setInterval(1000) # 1秒更新一次角度
|
||||||
|
|
||||||
|
# 重量读取定时器
|
||||||
|
self.timer_weight = QTimer() # 重量读取定时器
|
||||||
|
self.timer_weight.setInterval(2000) # 每2秒读取一次重量
|
||||||
|
|
||||||
|
# 绑定信号
|
||||||
|
self._connect_signals()
|
||||||
|
|
||||||
|
# 开启定时器
|
||||||
|
self.timer_angle.start()
|
||||||
|
self.timer_weight.start()
|
||||||
|
|
||||||
|
def _connect_signals(self):
|
||||||
|
# 更新上料斗重量
|
||||||
|
self.signals.upper_weight_updated.connect(self.onUpdateUpperHopperWeight)
|
||||||
|
|
||||||
|
# 上料斗重量定时读取
|
||||||
|
self.timer_weight.timeout.connect(self.handleReadUpperHopperWeight)
|
||||||
|
|
||||||
|
# 下料斗夹爪定时更新
|
||||||
|
self.timer_angle.timeout.connect(self.handleLowerClampAngleUpdate)
|
||||||
|
|
||||||
|
# 上料斗 "开"按钮点击
|
||||||
|
self.hopper_view.upper_open_btn.clicked.connect(self.onUpperClampOpenBottonClicked)
|
||||||
|
|
||||||
|
# 上料斗 "破拱"按钮
|
||||||
|
self.hopper_view.upper_arch_breaking_signal.connect(self.onUpperArchBreaking)
|
||||||
|
|
||||||
|
# 下料斗 "开"按钮点击
|
||||||
|
self.hopper_view.lower_open_btn.clicked.connect(self.onLowerClampOpenBottonClicked)
|
||||||
|
|
||||||
|
# 下料斗 "破拱"按钮
|
||||||
|
self.hopper_view.lower_arch_breaking_signal.connect(self.onLowerArchBreaking)
|
||||||
|
|
||||||
|
def handleLowerClampAngleUpdate(self):
|
||||||
|
"""处理下料斗夹爪开合"""
|
||||||
|
# 角度增减逻辑
|
||||||
|
if self.is_add:
|
||||||
|
self.angle += 1
|
||||||
|
else:
|
||||||
|
self.angle -= 1
|
||||||
|
|
||||||
|
# 边界控制
|
||||||
|
if self.angle > self.max_angle:
|
||||||
|
self.is_add = False
|
||||||
|
self.angle = self.max_angle
|
||||||
|
if self.angle <= self.min_angle:
|
||||||
|
self.is_add = True
|
||||||
|
self.angle = self.min_angle
|
||||||
|
|
||||||
|
# 更新下料斗夹爪角度
|
||||||
|
self.onUpdateLowerClampAngle(self.angle)
|
||||||
|
|
||||||
|
@Slot(int)
|
||||||
|
def onUpdateUpperHopperWeight(self, weight:int):
|
||||||
|
"更新上料斗重量"
|
||||||
|
self.hopper_view.setUpperHopperWeight(weight)
|
||||||
|
|
||||||
|
# 注意:此时需要同步更新传送带中的上料斗的重量
|
||||||
|
self.conveyor_view.setConveyorHopperWeight(weight)
|
||||||
|
|
||||||
|
@Slot(int)
|
||||||
|
def onUpdateLowerHopperWeight(self, weight:int):
|
||||||
|
"更新下料斗重量"
|
||||||
|
self.hopper_view.setLowerHopperWeight(weight)
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def handleReadUpperHopperWeight(self):
|
||||||
|
# 后台读取上料斗重量
|
||||||
|
def upper_weight_task():
|
||||||
|
loc_tra = TransmitterController(RelayController())
|
||||||
|
# 上料斗重量 (目前只有上料斗安装变送器, 可以读取到重量)
|
||||||
|
upper_weight = loc_tra.read_data(1)
|
||||||
|
# 发送信号到主线程更新UI
|
||||||
|
if upper_weight is not None:
|
||||||
|
self.signals.upper_weight_updated.emit(upper_weight)
|
||||||
|
|
||||||
|
threading.Thread(target=upper_weight_task, daemon=True).start()
|
||||||
|
|
||||||
|
@Slot(float)
|
||||||
|
def onUpdateLowerClampAngle(self, angle:float):
|
||||||
|
"""更新下料斗夹爪角度"""
|
||||||
|
self.hopper_view.setLowerHopperOpeningAngle(angle)
|
||||||
|
|
||||||
|
@Slot(float)
|
||||||
|
def onUpdateUpperClampAngle(self, angle:float):
|
||||||
|
"""更新上料斗夹爪角度"""
|
||||||
|
self.hopper_view.setUpperHopperClampAngle(angle)
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def onUpperClampOpenBottonClicked(self):
|
||||||
|
# 上料斗 夹爪 "开"按钮点击
|
||||||
|
print("hopper_controller: onUpperClampOpenBottonClicked")
|
||||||
|
# 测试上料斗夹爪,6秒打开60度
|
||||||
|
self.hopper_view.upper_clamp_widget.testAnimation(target_angle=60, duration=6)
|
||||||
|
|
||||||
|
@Slot(bool)
|
||||||
|
def onUpperArchBreaking(self, status:bool):
|
||||||
|
"""上料斗破拱: status 为True表示 开启破拱, 为False表示 关闭破拱"""
|
||||||
|
print("hopper_controller: onUpperArchBreaking ", status)
|
||||||
|
|
||||||
|
@Slot(int)
|
||||||
|
def onUpperHopperStatusChanged(self, status:int):
|
||||||
|
"""上料斗状态改变: status为 0=绿(正常), 1=黄(警告), 2=红(异常) """
|
||||||
|
# 料斗中的状态指示器
|
||||||
|
self.hopper_view.setUpperHopperStatus(status)
|
||||||
|
|
||||||
|
@Slot(float)
|
||||||
|
def onUpdateUpperHopperVolume(self, volume: float):
|
||||||
|
"""更新上料斗显示的方量,如: 2.0"""
|
||||||
|
self.hopper_view.setUpperHopperVolume(volume)
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def onLowerClampOpenBottonClicked(self):
|
||||||
|
# 下料斗 夹爪 "开"按钮点击
|
||||||
|
print("hopper_controller: onLowerClampOpenBottonClicked")
|
||||||
|
|
||||||
|
@Slot(bool)
|
||||||
|
def onLowerArchBreaking(self, status:bool):
|
||||||
|
"""下料斗破拱: status 为True表示 开启破拱, 为False表示 关闭破拱"""
|
||||||
|
print("hopper_controller: onLowerArchBreaking ", status)
|
||||||
|
|
||||||
|
@Slot(int)
|
||||||
|
def onLowerHopperStatusChanged(self, status:int):
|
||||||
|
"""下料斗状态改变: status为 0=绿(正常), 1=黄(警告), 2=红(异常) """
|
||||||
|
# 料斗中的状态指示器
|
||||||
|
self.hopper_view.setLowerHopperStatus(status)
|
||||||
@ -1,95 +1,47 @@
|
|||||||
from re import U
|
from PySide6.QtCore import QTimer, Signal, QObject # 导入Qt核心类
|
||||||
|
from PySide6.QtWidgets import QApplication # 用于获取主线程
|
||||||
|
import threading
|
||||||
from hardware import transmitter
|
from hardware import transmitter
|
||||||
from view.main_window import MainWindow
|
from view.main_window import MainWindow
|
||||||
import threading
|
|
||||||
from .camera_controller import CameraController
|
from .camera_controller import CameraController
|
||||||
from .bottom_control_controller import BottomControlController
|
from .bottom_control_controller import BottomControlController
|
||||||
from hardware.transmitter import TransmitterController
|
from .hopper_controller import HopperController
|
||||||
from hardware.relay import RelayController
|
|
||||||
|
|
||||||
class MainController:
|
class MainController:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# 主界面
|
# 主界面
|
||||||
self.main_window = MainWindow()
|
self.main_window = MainWindow()
|
||||||
# 定时器
|
|
||||||
self.timer = threading.Timer(5.0, self._onTimer)
|
|
||||||
self.timer.start() # 每5秒触发一次
|
|
||||||
|
|
||||||
self.timer2=threading.Timer(1.0, self._onTimer2)
|
# 初始化子界面和控制器
|
||||||
self.timer2.start() # 每秒触发一次
|
|
||||||
self.angle=10
|
|
||||||
self.max_angle=60
|
|
||||||
self.min_angle=10
|
|
||||||
self.is_add=True
|
|
||||||
# 初始化子界面
|
|
||||||
self._initSubViews()
|
self._initSubViews()
|
||||||
|
|
||||||
# 初始化子控制器
|
|
||||||
self._initSubControllers()
|
self._initSubControllers()
|
||||||
|
|
||||||
|
|
||||||
# self.__connectSignals()
|
|
||||||
|
|
||||||
def _onTimer(self):
|
|
||||||
# 定时任务逻辑
|
|
||||||
loc_tra=TransmitterController(RelayController())
|
|
||||||
upper_weight=loc_tra.read_data(1)
|
|
||||||
lower_weight=loc_tra.read_data(2)
|
|
||||||
if upper_weight is None:
|
|
||||||
upper_weight=0
|
|
||||||
|
|
||||||
if lower_weight is None:
|
|
||||||
lower_weight=0
|
|
||||||
|
|
||||||
self.main_window.hopper_widget.setUpperHopperWeight(upper_weight)
|
|
||||||
self.main_window.hopper_widget.setLowerHopperWeight(lower_weight)
|
|
||||||
# 重新启动定时器以实现重复执行
|
|
||||||
self.timer = threading.Timer(5.0, self._onTimer)
|
|
||||||
self.timer.start()
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _onTimer2(self):
|
|
||||||
print(str(self.angle))
|
|
||||||
# 定时任务逻辑
|
|
||||||
if self.is_add:
|
|
||||||
self.angle+=1
|
|
||||||
else:
|
|
||||||
self.angle-=1
|
|
||||||
|
|
||||||
if self.angle>self.max_angle:
|
|
||||||
self.is_add=False
|
|
||||||
self.angle=self.max_angle
|
|
||||||
if self.angle<=self.min_angle:
|
|
||||||
self.is_add=True
|
|
||||||
self.angle=10
|
|
||||||
|
|
||||||
self.main_window.hopper_widget.setLowerHopperOpeningAngle(self.angle)
|
|
||||||
# 重新启动定时器以实现重复执行
|
|
||||||
self.timer2 = threading.Timer(1.0, self._onTimer2)
|
|
||||||
self.timer2.start()
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def showMainWindow(self):
|
def showMainWindow(self):
|
||||||
self.main_window.show()
|
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("task1","15:44 PM")
|
||||||
self.main_window.dispatch_task_widget.set_task_time("task2","17:37 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")
|
self.main_window.segment_task_widget.set_task_time("task1","15:38 PM")
|
||||||
self.main_window.segment_task_widget.set_task_time("task2","17:24 PM")
|
self.main_window.segment_task_widget.set_task_time("task2","17:24 PM")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _initSubControllers(self):
|
def _initSubControllers(self):
|
||||||
# 振捣视频控制
|
# 右侧视频显示控制模块
|
||||||
self.camera_controller = CameraController(
|
self.camera_controller = CameraController(
|
||||||
video_view=self.main_window.vibration_video
|
video_view=self.main_window.vibration_video
|
||||||
)
|
)
|
||||||
# 底部控制(按钮)控制器
|
|
||||||
|
# 底部按钮控制模块
|
||||||
self.bottom_control_controller = BottomControlController(
|
self.bottom_control_controller = BottomControlController(
|
||||||
bottom_control_widget=self.main_window.bottom_control_widget,
|
bottom_control_widget=self.main_window.bottom_control_widget,
|
||||||
main_window=self.main_window
|
main_window=self.main_window
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 料斗控制模块(包括 夹爪开合、拱等按钮)
|
||||||
|
self.hopper_controller = HopperController(
|
||||||
|
hopper_view = self.main_window.hopper_widget,
|
||||||
|
conveyor_view = self.main_window.conveyor_system_widget
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _initSubViews(self):
|
def _initSubViews(self):
|
||||||
pass
|
pass
|
||||||
BIN
db/three.db
Normal file
@ -20,7 +20,8 @@ class TransmitterController:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def read_data(self, transmitter_id):
|
# 备份 (modbus 读取数据)
|
||||||
|
def read_data_bak(self, transmitter_id):
|
||||||
"""读取变送器数据"""
|
"""读取变送器数据"""
|
||||||
try:
|
try:
|
||||||
if transmitter_id not in self.config:
|
if transmitter_id not in self.config:
|
||||||
@ -67,3 +68,103 @@ class TransmitterController:
|
|||||||
return None
|
return None
|
||||||
finally:
|
finally:
|
||||||
self.relay_controller.modbus_client.close()
|
self.relay_controller.modbus_client.close()
|
||||||
|
|
||||||
|
# 直接读取 变送器返回的数据并解析
|
||||||
|
def read_data(self, transmitter_id):
|
||||||
|
"""
|
||||||
|
Args: transmitter_id 为1 表示上料斗, 为2 表示下料斗
|
||||||
|
return: 读取成功返回重量 weight: int, 失败返回 None
|
||||||
|
"""
|
||||||
|
if transmitter_id == 1:
|
||||||
|
# 上料斗变送器的信息:
|
||||||
|
IP = "192.168.250.63"
|
||||||
|
PORT = 502
|
||||||
|
TIMEOUT = 2 # 超时时间为 2秒
|
||||||
|
BUFFER_SIZE= 1024
|
||||||
|
weight = None
|
||||||
|
|
||||||
|
import socket
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
|
try:
|
||||||
|
s.settimeout(TIMEOUT)
|
||||||
|
s.connect((IP, PORT))
|
||||||
|
# print(f"连接上料斗变送器 {IP}:{PORT} 成功")
|
||||||
|
|
||||||
|
# 接收数据(变送器主动推送,recv即可获取数据)
|
||||||
|
data = s.recv(BUFFER_SIZE)
|
||||||
|
if data:
|
||||||
|
# print(f"收到原始数据:{data}")
|
||||||
|
|
||||||
|
# 提取出完整的一个数据包 (\r\n结尾)
|
||||||
|
packet = self.get_latest_valid_packet(data)
|
||||||
|
if not packet:
|
||||||
|
print("未获取到有效数据包!!")
|
||||||
|
return None
|
||||||
|
# 解析重量
|
||||||
|
weight = self.parse_weight(packet)
|
||||||
|
else:
|
||||||
|
print("未收到设备数据")
|
||||||
|
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
print(f"变送器连接失败:{IP}:{PORT} 拒绝连接(设备离线/端口错误)")
|
||||||
|
except socket.timeout:
|
||||||
|
print(f"读取变送器数据超时:{TIMEOUT}秒内未收到数据")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"读取异常:{e}")
|
||||||
|
|
||||||
|
# 成功返回重量(int),失败返回None
|
||||||
|
return weight
|
||||||
|
|
||||||
|
def get_latest_valid_packet(self, raw_data):
|
||||||
|
"""
|
||||||
|
解决TCP粘包:
|
||||||
|
从原始数据中,筛选所有有效包,返回最新的一个有效包
|
||||||
|
有效包标准: 1. 能UTF-8解码 2. 按逗号拆分≥3个字段 3. 第三个字段含数字(重量)
|
||||||
|
"""
|
||||||
|
DELIMITER = b'\r\n'
|
||||||
|
# 1. 按分隔符拆分,过滤空包
|
||||||
|
packets = [p for p in raw_data.split(DELIMITER) if p]
|
||||||
|
if not packets:
|
||||||
|
return None
|
||||||
|
|
||||||
|
valid_packets = []
|
||||||
|
for packet in packets:
|
||||||
|
try:
|
||||||
|
# 过滤无效ASCII字符(只保留可见字符)
|
||||||
|
valid_chars = [c for c in packet if 32 <= c <= 126]
|
||||||
|
filtered_packet = bytes(valid_chars)
|
||||||
|
# 2. 验证解码
|
||||||
|
data_str = filtered_packet.decode('utf-8').strip()
|
||||||
|
# 3. 验证字段数量
|
||||||
|
parts = data_str.split(',')
|
||||||
|
if len(parts) < 3:
|
||||||
|
continue
|
||||||
|
# 4. 验证重量字段含数字
|
||||||
|
weight_part = parts[2].strip()
|
||||||
|
if not any(char.isdigit() for char in weight_part):
|
||||||
|
continue
|
||||||
|
# 满足所有条件,加入有效包列表
|
||||||
|
valid_packets.append(packet)
|
||||||
|
except (UnicodeDecodeError, IndexError):
|
||||||
|
# 解码失败或字段异常,跳过该包
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 返回最后一个有效包(最新),无有效包则返回None
|
||||||
|
return valid_packets[-1] if valid_packets else None
|
||||||
|
|
||||||
|
def parse_weight(self, packet_data):
|
||||||
|
"""解析重量函数:提取重量数值(如从 b'ST,NT,+0000175\r\n' 中提取 175)"""
|
||||||
|
try:
|
||||||
|
data_str = packet_data.decode('utf-8').strip()
|
||||||
|
parts = data_str.split(',')
|
||||||
|
# 确保有完整的数据包,三个字段
|
||||||
|
if len(parts) < 3:
|
||||||
|
print(f"parse_weight: 包格式错误(字段不足):{data_str}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
weight_part = parts[2].strip()
|
||||||
|
return int(''.join(filter(str.isdigit, weight_part)))
|
||||||
|
except (IndexError, ValueError, UnicodeDecodeError) as e:
|
||||||
|
# print(f"数据解析失败:{e},原始数据包:{packet_data}")
|
||||||
|
return None
|
||||||
|
|
||||||
BIN
images/关闭图标.png
Normal file
|
After Width: | Height: | Size: 229 B |
BIN
images/派单任务信息栏1.png
Normal file
|
After Width: | Height: | Size: 510 B |
BIN
images/派单任务信息栏2.png
Normal file
|
After Width: | Height: | Size: 509 B |
BIN
images/管片任务信息栏.png
Normal file
|
After Width: | Height: | Size: 369 B |
BIN
images/系统诊断下拉箭头.png
Normal file
|
After Width: | Height: | Size: 172 B |
BIN
images/系统诊断小框.png
Normal file
|
After Width: | Height: | Size: 213 B |
BIN
images/系统诊断弹出背景.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
images/系统诊断毫秒背景.png
Normal file
|
After Width: | Height: | Size: 735 B |
BIN
images/系统诊断状态红.png
Normal file
|
After Width: | Height: | Size: 257 B |
BIN
images/系统诊断状态绿.png
Normal file
|
After Width: | Height: | Size: 251 B |
BIN
images/系统诊断状态黄.png
Normal file
|
After Width: | Height: | Size: 265 B |
BIN
images/详情弹出背景.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
images/详情标题.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
32
test.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import socket
|
||||||
|
|
||||||
|
# 设备信息
|
||||||
|
IP = "192.168.250.63"
|
||||||
|
PORT = 502
|
||||||
|
TIMEOUT = 5 # 超时时间(秒)
|
||||||
|
|
||||||
|
# 创建TCP socket
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
|
try:
|
||||||
|
s.settimeout(TIMEOUT) # 设置超时,避免一直阻塞
|
||||||
|
# 连接设备
|
||||||
|
s.connect((IP, PORT))
|
||||||
|
print(f"✅ 已通过TCP连接到 {IP}:{PORT}")
|
||||||
|
|
||||||
|
# 尝试接收数据(不发送任何请求,纯等待)
|
||||||
|
print("等待设备发送数据...(若5秒内无响应则超时)")
|
||||||
|
data = s.recv(1024) # 最多接收1024字节
|
||||||
|
|
||||||
|
if data:
|
||||||
|
# 打印收到的原始数据(16进制和字节列表)
|
||||||
|
# print(f"收到数据(16进制):{data.hex()}")
|
||||||
|
print(f"收到数据(字节列表):{list(data)}")
|
||||||
|
else:
|
||||||
|
print("❌ 未收到任何数据(设备未主动发送)")
|
||||||
|
|
||||||
|
except ConnectionRefusedError:
|
||||||
|
print(f"❌ 连接失败:{IP}:{PORT} 拒绝连接(设备离线/端口错误)")
|
||||||
|
except socket.timeout:
|
||||||
|
print(f"❌ 超时:{TIMEOUT}秒内未收到设备数据(设备未主动发送)")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 发生错误:{str(e)}")
|
||||||
@ -93,3 +93,25 @@ class ImagePaths:
|
|||||||
|
|
||||||
# 功能:主界面相关
|
# 功能:主界面相关
|
||||||
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"
|
||||||
|
|
||||||
|
# 功能: 派单任务详情按钮弹窗
|
||||||
|
DESPATCH_DETAILS_POPUP_BG = "images/详情弹出背景.png"
|
||||||
|
DESPATCH_DETAILS_TITLE_BG = "images/详情标题.png"
|
||||||
|
DESPATCH_DETAILS_INFO_BAR_NORMAL = "images/派单任务信息栏1.png"
|
||||||
|
DESPATCH_DETAILS_INFO_BAR_HOVER = "images/派单任务信息栏2.png"
|
||||||
|
DESPATCH_DETAILS_CLOSE_ICON = "images/关闭图标.png"
|
||||||
@ -21,6 +21,9 @@ from .widgets.bottom_control_widget import BottomControlWidget
|
|||||||
import resources.resources_rc
|
import resources.resources_rc
|
||||||
from utils.image_paths import ImagePaths
|
from utils.image_paths import ImagePaths
|
||||||
|
|
||||||
|
from .widgets.segment_details_dialog import SegmentDetailsDialog
|
||||||
|
from .widgets.dispatch_details_dialog import DispatchDetailsDialog
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QWidget):
|
class MainWindow(QWidget):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -34,21 +37,30 @@ class MainWindow(QWidget):
|
|||||||
# 安装事件过滤器,处理计划方量的 QLineEdit的失去焦点事件
|
# 安装事件过滤器,处理计划方量的 QLineEdit的失去焦点事件
|
||||||
self.installEventFilter(self)
|
self.installEventFilter(self)
|
||||||
|
|
||||||
|
# 连接槽函数
|
||||||
def connectSignalToSlot(self):
|
def connectSignalToSlot(self):
|
||||||
# 可添加信号槽连接
|
# 可添加信号槽连接
|
||||||
# self.system_button_widget.buttons["系统启动"].clicked.connect(self.handleSystemStart)
|
# self.system_button_widget.buttons["系统启动"].clicked.connect(self.handleSystemStart)
|
||||||
# self.system_button_widget.buttons["系统停止"].clicked.connect(self.handleSystemStop)
|
# 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) # 管片任务详情按钮
|
||||||
|
|
||||||
|
# 派单任务详情
|
||||||
|
self.dispatch_task_widget.task_details_signal.connect(self.handleDispatchTaskDetails) # 派单任务详情按钮
|
||||||
|
|
||||||
|
|
||||||
def handleSystemStart(self):
|
def handleSystemStart(self):
|
||||||
# 测试
|
# 测试系统开启,进度条动画
|
||||||
self.production_progress.testProgress(60)
|
self.production_progress.testProgress(60)
|
||||||
self.arc_progress.testProgress(60)
|
self.arc_progress.testProgress(60)
|
||||||
|
|
||||||
def handleSystemStop(self):
|
def handleSystemStop(self):
|
||||||
# 测试
|
# 测试系统停止,进度条动画
|
||||||
self.production_progress.animation.stop()
|
self.production_progress.animation.stop()
|
||||||
self.arc_progress.animation.stop()
|
self.arc_progress.animation.stop()
|
||||||
|
|
||||||
@ -63,7 +75,8 @@ class MainWindow(QWidget):
|
|||||||
# self.setStyleSheet("background-color: #ffffff;") # #001558
|
# self.setStyleSheet("background-color: #ffffff;") # #001558
|
||||||
|
|
||||||
# Qt.FramelessWindowHint
|
# Qt.FramelessWindowHint
|
||||||
self.setWindowFlags(Qt.FramelessWindowHint)
|
# 没有顶部的白色边框
|
||||||
|
self.setWindowFlags(Qt.FramelessWindowHint) # 无边框
|
||||||
|
|
||||||
# 设置主界面背景图片
|
# 设置主界面背景图片
|
||||||
try:
|
try:
|
||||||
@ -100,6 +113,7 @@ class MainWindow(QWidget):
|
|||||||
self.dispatch_task_widget.set_task_id("task2", "PD0002")
|
self.dispatch_task_widget.set_task_id("task2", "PD0002")
|
||||||
self.dispatch_task_widget.set_task_id("task3", "PD0003")
|
self.dispatch_task_widget.set_task_id("task3", "PD0003")
|
||||||
|
|
||||||
|
# 读取数据库,初始化 管片任务的数据
|
||||||
from busisness.blls import ArtifactBll, PDRecordBll
|
from busisness.blls import ArtifactBll, PDRecordBll
|
||||||
artifact_dal = ArtifactBll()
|
artifact_dal = ArtifactBll()
|
||||||
artifacts = artifact_dal.get_artifact_task()
|
artifacts = artifact_dal.get_artifact_task()
|
||||||
@ -197,6 +211,8 @@ class MainWindow(QWidget):
|
|||||||
# 以下为模拟:
|
# 以下为模拟:
|
||||||
# 假设两秒种之后,移动到了搅拌机下 (这里需要根据实际情况修改)
|
# 假设两秒种之后,移动到了搅拌机下 (这里需要根据实际情况修改)
|
||||||
QTimer.singleShot(2000, self.conveyor_system_widget.moveHopperBelowMixer)
|
QTimer.singleShot(2000, self.conveyor_system_widget.moveHopperBelowMixer)
|
||||||
|
# 移动到搅拌楼下,搅拌桨就开始旋转
|
||||||
|
QTimer.singleShot(2100, self.mixer_widget.startBladeMix)
|
||||||
# 料斗左移完成,恢复料斗右移按钮
|
# 料斗左移完成,恢复料斗右移按钮
|
||||||
QTimer.singleShot(2100, lambda: self.conveyor_system_widget.right_btn.setEnabled(True))
|
QTimer.singleShot(2100, lambda: self.conveyor_system_widget.right_btn.setEnabled(True))
|
||||||
|
|
||||||
@ -207,6 +223,10 @@ class MainWindow(QWidget):
|
|||||||
self.hopper_widget.upper_clamp_widget.set_angle(0) # 上料斗向右移动到目的地时,夹爪的角度一定是0
|
self.hopper_widget.upper_clamp_widget.set_angle(0) # 上料斗向右移动到目的地时,夹爪的角度一定是0
|
||||||
# 按钮状态:点击料斗右移按钮后,禁用料斗左移按钮
|
# 按钮状态:点击料斗右移按钮后,禁用料斗左移按钮
|
||||||
self.conveyor_system_widget.left_btn.setEnabled(False)
|
self.conveyor_system_widget.left_btn.setEnabled(False)
|
||||||
|
|
||||||
|
# 开始右移,搅拌桨就停止转动
|
||||||
|
self.mixer_widget.stopBladeMix()
|
||||||
|
|
||||||
# 以下为模拟:
|
# 以下为模拟:
|
||||||
# 假设两秒后,传送带中 料斗向右移动完成 (这里需要根据实际情况修改)
|
# 假设两秒后,传送带中 料斗向右移动完成 (这里需要根据实际情况修改)
|
||||||
QTimer.singleShot(1900, self.conveyor_system_widget.hideHopper) # 料斗向右移动完成,隐藏料斗
|
QTimer.singleShot(1900, self.conveyor_system_widget.hideHopper) # 料斗向右移动完成,隐藏料斗
|
||||||
@ -214,6 +234,42 @@ class MainWindow(QWidget):
|
|||||||
# 料斗右移完成,恢复料斗左移按钮
|
# 料斗右移完成,恢复料斗左移按钮
|
||||||
QTimer.singleShot(2100, lambda: self.conveyor_system_widget.left_btn.setEnabled(True))
|
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()
|
||||||
|
|
||||||
|
def handleDispatchTaskDetails(self, dispatch_task_name:str):
|
||||||
|
# 派单任务名 task1、task2、task3 (分别对应第一条派单任务、 第二条派单任务...)
|
||||||
|
print("main_window: handleDispatchTaskDetails", dispatch_task_name)
|
||||||
|
|
||||||
|
# 显示派单任务详情对话框
|
||||||
|
dispatch_details_dialog = DispatchDetailsDialog(dispatch_task_name, self)
|
||||||
|
|
||||||
|
# 这里可以设置对话框显示的内容 如 set_segment_id
|
||||||
|
# dispatch_details_dialog.set_segment_id("9999999999")
|
||||||
|
# 设置派单任务详情中的方量的值
|
||||||
|
current_volume = self.dispatch_task_widget.get_task_volume(dispatch_task_name)
|
||||||
|
dispatch_details_dialog.set_row_value(4, str(current_volume)) # 派单方量的值的行号为4,第五行
|
||||||
|
|
||||||
|
# 派单任务详情页面中确定修改了派单任务的方量
|
||||||
|
# 备注:褚工说管片任务和派单任务中的方量都只有一位小数,料斗上的方量显示两位 2025/11/8
|
||||||
|
dispatch_details_dialog.confirm_modify_volume.connect(self.handleModifyDispatchTaskVolume)
|
||||||
|
dispatch_details_dialog.show()
|
||||||
|
|
||||||
|
def handleModifyDispatchTaskVolume(self, dispatch_task_name:str, modifyed_volume:float):
|
||||||
|
"""派单任务详情页面中, 修改了派单任务的方量"""
|
||||||
|
# 修改相应的派单任务条目显示的 派单任务方量
|
||||||
|
self.dispatch_task_widget.set_task_volume(dispatch_task_name, modifyed_volume)
|
||||||
|
|
||||||
|
# 其他操作,可能需要修改数据库的派单任务方量
|
||||||
|
|
||||||
|
|
||||||
# 更新 派单任务widget的坐标
|
# 更新 派单任务widget的坐标
|
||||||
def update_dispatch_task_position(self):
|
def update_dispatch_task_position(self):
|
||||||
# 方法1:获取模具车控件左上角坐标(相对于父控件)
|
# 方法1:获取模具车控件左上角坐标(相对于父控件)
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from view.widgets.switch_button import SwitchButton
|
|||||||
import resources.resources_rc
|
import resources.resources_rc
|
||||||
from utils.image_paths import ImagePaths
|
from utils.image_paths import ImagePaths
|
||||||
|
|
||||||
|
# 底部的控制控件,包括 系统诊断、系统中心等按钮
|
||||||
class BottomControlWidget(QWidget):
|
class BottomControlWidget(QWidget):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@ -262,8 +263,9 @@ class BottomControlWidget(QWidget):
|
|||||||
text_label.setStyleSheet("color: #3bfff8; font-size: 20px;")
|
text_label.setStyleSheet("color: #3bfff8; font-size: 20px;")
|
||||||
layout.addWidget(text_label, alignment=Qt.AlignVCenter)
|
layout.addWidget(text_label, alignment=Qt.AlignVCenter)
|
||||||
|
|
||||||
# 开关
|
# 开关(初始的时候,自动模式是打开的)
|
||||||
self.auto_switch = SwitchButton()
|
self.auto_switch = SwitchButton()
|
||||||
|
self.auto_switch.setChecked(True) # 设置自动模式初始状态
|
||||||
layout.addWidget(self.auto_switch, alignment=Qt.AlignVCenter | Qt.AlignRight)
|
layout.addWidget(self.auto_switch, alignment=Qt.AlignVCenter | Qt.AlignRight)
|
||||||
|
|
||||||
return widget
|
return widget
|
||||||
|
|||||||
@ -18,6 +18,9 @@ class ConveyorSystemWidget(QWidget):
|
|||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setWindowTitle("料斗与传送带界面")
|
self.setWindowTitle("料斗与传送带界面")
|
||||||
|
|
||||||
|
self._last_upper_hopper_weight = None # 上一次的上料斗重量(初始为None)
|
||||||
|
|
||||||
self.setFixedSize(443, 190)
|
self.setFixedSize(443, 190)
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
self._bind()
|
self._bind()
|
||||||
@ -74,13 +77,52 @@ class ConveyorSystemWidget(QWidget):
|
|||||||
inner_img = ImagePaths.HOPPER2
|
inner_img = ImagePaths.HOPPER2
|
||||||
inner_pixmap = QPixmap(inner_img)
|
inner_pixmap = QPixmap(inner_img)
|
||||||
if not inner_pixmap.isNull():
|
if not inner_pixmap.isNull():
|
||||||
upper_inner_label = QLabel(upper_bg_widget)
|
self.upper_inner_label = QLabel(upper_bg_widget)
|
||||||
upper_inner_label.setPixmap(inner_pixmap)
|
self.upper_inner_label.setPixmap(inner_pixmap)
|
||||||
upper_inner_label.setFixedSize(inner_pixmap.width(), inner_pixmap.height())
|
self.upper_inner_label.setFixedSize(inner_pixmap.width(), inner_pixmap.height())
|
||||||
upper_inner_label.move(14, 9) # 保持原位置
|
self.upper_inner_label.setScaledContents(False)
|
||||||
|
self.upper_inner_label.setStyleSheet("background: none;")
|
||||||
|
self.upper_inner_label.move(14, 9)
|
||||||
|
self.upper_inner_label.setAlignment(Qt.AlignBottom)
|
||||||
|
|
||||||
return group
|
return group
|
||||||
|
|
||||||
|
def _update_upper_inner_height(self, total_weight, current_weight: float):
|
||||||
|
"""根据当前重量占比, 更新upper_inner_label的高度, 实现动态进度的效果"""
|
||||||
|
# 1、处理边界值(超过总重量按100%,低于0按0%)
|
||||||
|
clamped_weight = max(0.0, min(current_weight, total_weight))
|
||||||
|
|
||||||
|
# 2、计算占比(0~1之间)
|
||||||
|
weight_ratio = clamped_weight / (total_weight * 1.0)
|
||||||
|
|
||||||
|
# 3、根据占比计算实际高度
|
||||||
|
inner_img_height = 100 # 内部的料斗阴影的高度为100px
|
||||||
|
target_height = int(weight_ratio * inner_img_height)
|
||||||
|
|
||||||
|
# print("target_height: ", target_height)
|
||||||
|
|
||||||
|
# 4、设置标签高度(动态变化)
|
||||||
|
self.upper_inner_label.setFixedHeight(target_height)
|
||||||
|
|
||||||
|
# 5、计算标签位置(确保标签底部与父容器底部对齐)
|
||||||
|
container_bottom = 117 # 容器的高固定为了 117px (背景图片"料斗1"的高)
|
||||||
|
label_y = container_bottom - target_height - 8 # 标签顶部y坐标 (减去底部8px)
|
||||||
|
self.upper_inner_label.move(14, label_y) # x固定,y动态计算
|
||||||
|
# print("label_y", label_y)
|
||||||
|
|
||||||
|
# 6、强制刷新UI,确保立即显示变化
|
||||||
|
self.upper_inner_label.update()
|
||||||
|
|
||||||
|
def setConveyorHopperWeight(self, weight:int):
|
||||||
|
if weight != self._last_upper_hopper_weight:
|
||||||
|
# 1、更新传送带中的 上料斗内部进度显示
|
||||||
|
# 假设上料斗装满之后,总的重量为 5100kg (褚工说设置为 6000kg 11/6)
|
||||||
|
total_weight = 6000
|
||||||
|
self._update_upper_inner_height(total_weight, weight)
|
||||||
|
|
||||||
|
# 2、将self._last_upper_hopper_weight设置为当前重量
|
||||||
|
self._last_upper_hopper_weight = weight
|
||||||
|
|
||||||
def create_conveyor(self):
|
def create_conveyor(self):
|
||||||
"""创建传送带组件(包含左右齿轮,group容器背景为传送带图片)"""
|
"""创建传送带组件(包含左右齿轮,group容器背景为传送带图片)"""
|
||||||
group = QWidget()
|
group = QWidget()
|
||||||
|
|||||||
391
view/widgets/dispatch_details_dialog.py
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
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, QEvent, Signal
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from utils.image_paths import ImagePaths
|
||||||
|
|
||||||
|
from view.widgets.value_adjuster import ValueAdjuster
|
||||||
|
|
||||||
|
"""
|
||||||
|
派单任务的详情按钮点击之后弹出, 显示派单任务的详情
|
||||||
|
"""
|
||||||
|
|
||||||
|
class DispatchDetailsDialog(QDialog):
|
||||||
|
# 确认修改了派单任务的方量,发送任务名(task1、task2等)和最终确认修改的方量值
|
||||||
|
confirm_modify_volume = Signal(str, float)
|
||||||
|
|
||||||
|
def __init__(self, dispatch_task_name:str, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setAttribute(Qt.WA_TranslucentBackground)
|
||||||
|
|
||||||
|
# 派单任务名 (task1、task2、 task3)
|
||||||
|
self.dispatch_task_name = dispatch_task_name
|
||||||
|
|
||||||
|
# 初始化存储需要修改的控件
|
||||||
|
self.id_value_label = None # 对应管片ID值标签
|
||||||
|
self.rows = [] # 所有行的单元格列表(包含label、value)
|
||||||
|
|
||||||
|
# 派单方量调整控件,用于修改派单方量
|
||||||
|
self.volume_value_adjuster = None
|
||||||
|
|
||||||
|
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区域
|
||||||
|
self._add_segment_id_area(main_layout)
|
||||||
|
|
||||||
|
# 3. 网格信息区域(单列7行)
|
||||||
|
self._add_grid_info_area(main_layout)
|
||||||
|
|
||||||
|
# 4. 修改方量按钮
|
||||||
|
self.modify_btn = QPushButton("修改方量", parent=self)
|
||||||
|
self.modify_btn.setFixedSize(89, 32)
|
||||||
|
self.modify_btn.setStyleSheet(
|
||||||
|
"""
|
||||||
|
QPushButton {
|
||||||
|
background-color: #001c82;
|
||||||
|
color: #9fbfd4;
|
||||||
|
border: 1px solid #017cbc;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: Bold;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
color: #2dcedb;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
# modify_btn.move(860, 446) # 移动到第五行,派单方量的位置
|
||||||
|
self.modify_btn.move(860, 442) # 移动到第五行,派单方量的位置
|
||||||
|
self.modify_btn.clicked.connect(self.onModifyVolume)
|
||||||
|
|
||||||
|
# 确认修改方量按钮,表示 派单方量的修改已经确定
|
||||||
|
self.confirm_btn = QPushButton("确定", parent=self)
|
||||||
|
self.confirm_btn.setStyleSheet(
|
||||||
|
"""
|
||||||
|
QPushButton {
|
||||||
|
background-color: #001c82;
|
||||||
|
color: #9fbfd4;
|
||||||
|
border: 1px solid #017cbc;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: Bold;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
color: #2dcedb;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
self.confirm_btn.move(860, 442)
|
||||||
|
self.confirm_btn.hide() # 初始隐藏
|
||||||
|
self.confirm_btn.setFixedSize(42, 32)
|
||||||
|
self.confirm_btn.clicked.connect(self.onConfirmModifyVolume)
|
||||||
|
|
||||||
|
# 取消修改方量按钮,表示 派单方量的修改已经取消
|
||||||
|
self.cancel_btn = QPushButton("取消", parent=self)
|
||||||
|
self.cancel_btn.setStyleSheet(
|
||||||
|
"""
|
||||||
|
QPushButton {
|
||||||
|
background-color: #001c82;
|
||||||
|
color: #9fbfd4;
|
||||||
|
border: 1px solid #017cbc;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: Bold;
|
||||||
|
}
|
||||||
|
QPushButton:hover {
|
||||||
|
color: #2dcedb;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
self.cancel_btn.hide()
|
||||||
|
self.cancel_btn.setFixedSize(42, 32)
|
||||||
|
self.cancel_btn.move(907, 442)
|
||||||
|
self.cancel_btn.clicked.connect(self.onCancelModifyVolume)
|
||||||
|
|
||||||
|
def _load_background(self):
|
||||||
|
self.bg_pixmap = QPixmap(ImagePaths.DESPATCH_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)
|
||||||
|
|
||||||
|
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):
|
||||||
|
self.close_btn = QPushButton()
|
||||||
|
self.close_btn.setFixedSize(36, 36)
|
||||||
|
|
||||||
|
close_icon = QPixmap(ImagePaths.DESPATCH_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_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.DESPATCH_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)
|
||||||
|
|
||||||
|
self.id_value_label = QLabel("222232454352452") # 初始管片ID值
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 初始化信息条目(7行)
|
||||||
|
info_items = [
|
||||||
|
("创建时间", "2025年10月10日 10:10:10"),
|
||||||
|
("派单时间", "2025年10月10日 10:10:10"),
|
||||||
|
("任务编号", "20251010-10"),
|
||||||
|
("配比编号", "20251010-10"),
|
||||||
|
("派单方量", "2.0"),
|
||||||
|
("派单状态", "未下发"),
|
||||||
|
("派单类型", "自动派单"),
|
||||||
|
]
|
||||||
|
|
||||||
|
self.rows.clear()
|
||||||
|
for row, (label_text, value_text) in enumerate(info_items):
|
||||||
|
cell_widget = self._create_info_cell(label_text, value_text)
|
||||||
|
self.rows.append(cell_widget)
|
||||||
|
grid_layout.addWidget(cell_widget, row, 0)
|
||||||
|
|
||||||
|
parent_layout.addLayout(grid_layout)
|
||||||
|
|
||||||
|
def _create_info_cell(self, label_text, value_text):
|
||||||
|
cell_widget = QWidget()
|
||||||
|
cell_bg = QPixmap(ImagePaths.DESPATCH_DETAILS_INFO_BAR_NORMAL) # 正常背景图
|
||||||
|
cell_widget.setObjectName("infoCell")
|
||||||
|
if not cell_bg.isNull():
|
||||||
|
cell_widget.setFixedSize(cell_bg.size())
|
||||||
|
cell_widget.setStyleSheet(
|
||||||
|
f"""
|
||||||
|
QWidget {{
|
||||||
|
background-image: url({ImagePaths.DESPATCH_DETAILS_INFO_BAR_NORMAL});
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: Center;
|
||||||
|
}}
|
||||||
|
QWidget:hover {{
|
||||||
|
background-image: url({ImagePaths.DESPATCH_DETAILS_INFO_BAR_HOVER});
|
||||||
|
}}
|
||||||
|
QWidget QLabel#valueLabel {{
|
||||||
|
color: #9fbfd4;
|
||||||
|
background: none;
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
cell_layout = QHBoxLayout(cell_widget)
|
||||||
|
cell_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
# 左侧标签
|
||||||
|
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;color: #fffffd; font-weight:Bold;")
|
||||||
|
label.setAlignment(Qt.AlignCenter)
|
||||||
|
cell_widget.label = label
|
||||||
|
|
||||||
|
# 右侧值标签(设置objectName以便样式选择)
|
||||||
|
value = QLabel(value_text)
|
||||||
|
value.setObjectName("valueLabel")
|
||||||
|
value_font = QFont()
|
||||||
|
value_font.setPixelSize(20)
|
||||||
|
value.setFont(value_font)
|
||||||
|
value.setAlignment(Qt.AlignCenter)
|
||||||
|
cell_widget.value = value
|
||||||
|
|
||||||
|
cell_layout.addWidget(label) # 左侧的标题标签
|
||||||
|
cell_layout.addSpacing(60)
|
||||||
|
cell_layout.addWidget(value) # 右侧的值标签
|
||||||
|
|
||||||
|
cell_widget.installEventFilter(self)
|
||||||
|
return cell_widget
|
||||||
|
|
||||||
|
# 实现事件过滤器,动态修改右侧值颜色
|
||||||
|
def eventFilter(self, obj, event):
|
||||||
|
# 只处理父控件(infoCell)的事件
|
||||||
|
if obj.objectName() == "infoCell":
|
||||||
|
# 鼠标进入父控件 → 改#13f0f3
|
||||||
|
if event.type() == QEvent.Enter:
|
||||||
|
if hasattr(obj, "value"): # 确保存在value控件
|
||||||
|
obj.value.setStyleSheet("background: none; color: #13f0f3;")
|
||||||
|
# 鼠标离开父控件 → 恢复默认色
|
||||||
|
elif event.type() == QEvent.Leave:
|
||||||
|
if hasattr(obj, "value"):
|
||||||
|
obj.value.setStyleSheet("background: none; color: #9fbfd4;")
|
||||||
|
return super().eventFilter(obj, event)
|
||||||
|
|
||||||
|
def onModifyVolume(self):
|
||||||
|
"""修改派单方量的逻辑"""
|
||||||
|
volume_label = self.rows[4].value
|
||||||
|
current_value = float(volume_label.text())
|
||||||
|
|
||||||
|
# 1、调整派单方量,创建派单方量调整控件
|
||||||
|
if not self.volume_value_adjuster:
|
||||||
|
self.volume_value_adjuster = ValueAdjuster(self)
|
||||||
|
self.volume_value_adjuster.move(551, 442) # 移动到当前显示派单方量的标签处
|
||||||
|
|
||||||
|
# 2、更新派单方量调整控件的值, 并显示
|
||||||
|
self.volume_value_adjuster.set_value(current_value)
|
||||||
|
self.volume_value_adjuster.show()
|
||||||
|
|
||||||
|
# 3、显示确定按钮、显示取消按钮、隐藏修改方量按钮
|
||||||
|
self.confirm_btn.show()
|
||||||
|
self.cancel_btn.show()
|
||||||
|
self.modify_btn.hide()
|
||||||
|
|
||||||
|
def onConfirmModifyVolume(self):
|
||||||
|
"""确定 修改派单方量"""
|
||||||
|
# 显示相关的:
|
||||||
|
# 1、隐藏确认按钮、隐藏取消按钮、显示修改方量按钮
|
||||||
|
self.confirm_btn.hide()
|
||||||
|
self.cancel_btn.hide()
|
||||||
|
self.modify_btn.show()
|
||||||
|
|
||||||
|
# 2、修改 派单方量标签的值
|
||||||
|
volume_label = self.rows[4].value
|
||||||
|
# modifyed_value 为float类型, 一位小数
|
||||||
|
modifyed_value = self.volume_value_adjuster.get_value()
|
||||||
|
volume_label.setText(str(modifyed_value))
|
||||||
|
|
||||||
|
# 3、发送派单方量确定修改的信号 (发送派单任务名 + 确认修改之后的派单方量)
|
||||||
|
self.confirm_modify_volume.emit(self.dispatch_task_name, modifyed_value)
|
||||||
|
|
||||||
|
# 4、关闭派单方量调整控件
|
||||||
|
self.volume_value_adjuster.close()
|
||||||
|
|
||||||
|
def onCancelModifyVolume(self):
|
||||||
|
# 显示相关的:
|
||||||
|
# 1、隐藏确认按钮、隐藏取消按钮、显示修改方量按钮
|
||||||
|
self.confirm_btn.hide()
|
||||||
|
self.cancel_btn.hide()
|
||||||
|
self.modify_btn.show()
|
||||||
|
|
||||||
|
# 2、关闭派单方量调整控件
|
||||||
|
self.volume_value_adjuster.close()
|
||||||
|
|
||||||
|
def paintEvent(self, event):
|
||||||
|
if not self.bg_pixmap.isNull():
|
||||||
|
painter = QPainter(self)
|
||||||
|
painter.drawPixmap(self.rect(), self.bg_pixmap)
|
||||||
|
super().paintEvent(event)
|
||||||
|
|
||||||
|
# ------------------- 对外修改接口 -------------------
|
||||||
|
# row 对应行号(0-6),从0开始
|
||||||
|
# --------------------------------------------------
|
||||||
|
def set_segment_id(self, new_id):
|
||||||
|
"""修改上方的 对应的管片ID的值"""
|
||||||
|
if self.id_value_label:
|
||||||
|
self.id_value_label.setText(str(new_id))
|
||||||
|
|
||||||
|
def set_row_label(self, row, new_label_text: str):
|
||||||
|
"""修改左侧的显示的标签的文本,如: 创建时间、派单时间等"""
|
||||||
|
if 0 <= row < len(self.rows):
|
||||||
|
self.rows[row].label.setText(new_label_text)
|
||||||
|
|
||||||
|
def set_row_value(self, row, new_value_text: str):
|
||||||
|
"""修改右侧的显示的值, 如: 2025年9月9日 9:9:9"""
|
||||||
|
if 0 <= row < len(self.rows):
|
||||||
|
self.rows[row].value.setText(new_value_text)
|
||||||
|
|
||||||
|
|
||||||
|
# 测试代码
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
dialog = DispatchDetailsDialog()
|
||||||
|
|
||||||
|
# 测试修改接口
|
||||||
|
dialog.set_segment_id("999999999999999")
|
||||||
|
dialog.set_row_label(0, "新创建时间")
|
||||||
|
dialog.set_row_value(0, "2025年09月09日 09:09:09")
|
||||||
|
dialog.set_row_value(4, "3.0") # 初始派单方量修改
|
||||||
|
|
||||||
|
dialog.show()
|
||||||
|
sys.exit(app.exec())
|
||||||
@ -29,6 +29,10 @@ class HopperWidget(QWidget):
|
|||||||
self.upper_arch_breaking_status = False # 初始为不破拱状态
|
self.upper_arch_breaking_status = False # 初始为不破拱状态
|
||||||
self.lower_arch_breaking_status = False # 初始为不破拱状态
|
self.lower_arch_breaking_status = False # 初始为不破拱状态
|
||||||
|
|
||||||
|
# 上一次获取到的料斗的当前重量
|
||||||
|
self._last_upper_hopper_weight = None # 上一次的上料斗重量(初始为None)
|
||||||
|
self._last_lower_hopper_weight = None # 上一次的下料斗重量(初始为None)
|
||||||
|
|
||||||
# 料斗控制界面的固定大小为 332x482,
|
# 料斗控制界面的固定大小为 332x482,
|
||||||
# 需要根据具体的料斗的图片来调整
|
# 需要根据具体的料斗的图片来调整
|
||||||
# self.setFixedSize(356, 496)
|
# self.setFixedSize(356, 496)
|
||||||
@ -90,17 +94,20 @@ class HopperWidget(QWidget):
|
|||||||
self.upper_bg_widget = QWidget()
|
self.upper_bg_widget = QWidget()
|
||||||
self.upper_bg_widget.setFixedSize(outer_width, outer_height)
|
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;")
|
self.upper_bg_widget.setStyleSheet(f"background-image: url({outer_img});background-repeat: no-repeat; background-position: center;")
|
||||||
|
# self.upper_bg_widget.setStyleSheet(f"background-color:red; background-repeat: no-repeat; background-position: center;")
|
||||||
layout.addWidget(self.upper_bg_widget, alignment=Qt.AlignCenter)
|
layout.addWidget(self.upper_bg_widget, alignment=Qt.AlignCenter)
|
||||||
|
|
||||||
|
|
||||||
# 内框图片(上位)
|
# 内框图片(上位)
|
||||||
inner_img = ImagePaths.HOPPER2
|
inner_img = ImagePaths.HOPPER2
|
||||||
inner_pixmap = QPixmap(inner_img)
|
inner_pixmap = QPixmap(inner_img)
|
||||||
if not inner_pixmap.isNull():
|
if not inner_pixmap.isNull():
|
||||||
self.upper_inner_label = QLabel(self.upper_bg_widget)
|
self.upper_inner_label = QLabel(self.upper_bg_widget)
|
||||||
self.upper_inner_label.setPixmap(inner_pixmap)
|
self.upper_inner_label.setPixmap(inner_pixmap)
|
||||||
self.upper_inner_label.setFixedSize(inner_pixmap.width(), inner_pixmap.height())
|
self.upper_inner_label.setFixedSize(inner_pixmap.width(), inner_pixmap.height()) # 初始宽高
|
||||||
|
self.upper_inner_label.setScaledContents(False) # 禁用缩放(避免图片拉伸)
|
||||||
|
self.upper_inner_label.setStyleSheet("background: none;")
|
||||||
self.upper_inner_label.move(14, 9)
|
self.upper_inner_label.move(14, 9)
|
||||||
|
self.upper_inner_label.setAlignment(Qt.AlignBottom)
|
||||||
|
|
||||||
# 状态图片(上位,绿色)
|
# 状态图片(上位,绿色)
|
||||||
status_img = ImagePaths.HOPPER_STATUS_GREEN
|
status_img = ImagePaths.HOPPER_STATUS_GREEN
|
||||||
@ -162,17 +169,6 @@ class HopperWidget(QWidget):
|
|||||||
self.upper_arch_btn.clicked.connect(self.onUpperArchBreaking)
|
self.upper_arch_btn.clicked.connect(self.onUpperArchBreaking)
|
||||||
self.lower_arch_btn.clicked.connect(self.onLowerArchBreaking)
|
self.lower_arch_btn.clicked.connect(self.onLowerArchBreaking)
|
||||||
|
|
||||||
self.upper_open_btn.clicked.connect(self.onUpperClampOpen)
|
|
||||||
self.lower_open_btn.clicked.connect(self.onLowerClampOpen)
|
|
||||||
|
|
||||||
@Slot()
|
|
||||||
def onUpperClampOpen(self):
|
|
||||||
self.upper_clamp_widget.testAnimation(target_angle=60, duration=6) # 测试,6秒打开60度
|
|
||||||
|
|
||||||
@Slot()
|
|
||||||
def onLowerClampOpen(self):
|
|
||||||
self.lower_clamp_widget.testAnimation(target_angle=25, duration=6) # 测试,6秒打开30度
|
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def onUpperArchBreaking(self):
|
def onUpperArchBreaking(self):
|
||||||
if self.upper_arch_breaking_status == False: # 不破拱状态
|
if self.upper_arch_breaking_status == False: # 不破拱状态
|
||||||
@ -251,7 +247,10 @@ class HopperWidget(QWidget):
|
|||||||
self.lower_inner_label = QLabel(self.lower_bg_widget)
|
self.lower_inner_label = QLabel(self.lower_bg_widget)
|
||||||
self.lower_inner_label.setPixmap(inner_pixmap)
|
self.lower_inner_label.setPixmap(inner_pixmap)
|
||||||
self.lower_inner_label.setFixedSize(inner_pixmap.width(), inner_pixmap.height())
|
self.lower_inner_label.setFixedSize(inner_pixmap.width(), inner_pixmap.height())
|
||||||
|
self.lower_inner_label.setScaledContents(False) # 禁用图片缩放
|
||||||
|
self.lower_inner_label.setStyleSheet("background: none;")
|
||||||
self.lower_inner_label.move(14, 9)
|
self.lower_inner_label.move(14, 9)
|
||||||
|
self.lower_inner_label.setAlignment(Qt.AlignBottom)
|
||||||
|
|
||||||
# 状态图片(下位)
|
# 状态图片(下位)
|
||||||
status_img = ImagePaths.HOPPER_STATUS_GREEN
|
status_img = ImagePaths.HOPPER_STATUS_GREEN
|
||||||
@ -313,10 +312,48 @@ class HopperWidget(QWidget):
|
|||||||
|
|
||||||
return group
|
return group
|
||||||
|
|
||||||
|
def _update_upper_inner_height(self, total_weight, current_weight: float):
|
||||||
|
"""根据当前重量占比, 更新upper_inner_label的高度, 实现动态进度的效果"""
|
||||||
|
# 1、处理边界值(超过总重量按100%,低于0按0%)
|
||||||
|
clamped_weight = max(0.0, min(current_weight, total_weight))
|
||||||
|
|
||||||
|
# 2、计算占比(0~1之间)
|
||||||
|
weight_ratio = clamped_weight / (total_weight * 1.0)
|
||||||
|
|
||||||
|
# 3、根据占比计算实际高度
|
||||||
|
inner_img_height = 100 # 内部的料斗阴影的高度为100px
|
||||||
|
target_height = int(weight_ratio * inner_img_height)
|
||||||
|
|
||||||
|
# print("target_height: ", target_height)
|
||||||
|
|
||||||
|
# 4、设置标签高度(动态变化)
|
||||||
|
self.upper_inner_label.setFixedHeight(target_height)
|
||||||
|
|
||||||
|
# 5、计算标签位置(确保标签底部与父容器底部对齐)
|
||||||
|
# container_bottom = self.upper_bg_widget.y() + self.upper_bg_widget.height()
|
||||||
|
container_bottom = 117 # 容器的高固定为了 117px (背景图片"料斗1"的高)
|
||||||
|
label_y = container_bottom - target_height - 8 # 标签顶部y坐标 (减去底部8px)
|
||||||
|
self.upper_inner_label.move(14, label_y) # x固定,y动态计算
|
||||||
|
# print("current_weight",current_weight, "label_y", label_y)
|
||||||
|
|
||||||
|
# 6、强制刷新UI,确保立即显示变化
|
||||||
|
self.upper_inner_label.update()
|
||||||
|
|
||||||
# 上料斗重量设置
|
# 上料斗重量设置
|
||||||
def setUpperHopperWeight(self, weight:float):
|
def setUpperHopperWeight(self, weight:int):
|
||||||
|
# 仅当重量变化时,才更新标签和进度
|
||||||
|
if weight != self._last_upper_hopper_weight:
|
||||||
|
# 1、更新上料斗重量标签,显示最新重量
|
||||||
self.upper_weight_label.setText(f"{weight}kg")
|
self.upper_weight_label.setText(f"{weight}kg")
|
||||||
|
|
||||||
|
# 2、更新上料斗内部进度显示
|
||||||
|
# 假设上料斗装满之后,总的重量为 5100kg (褚工说设置为 6000kg 11/6)
|
||||||
|
total_weight = 6000
|
||||||
|
self._update_upper_inner_height(total_weight, weight)
|
||||||
|
|
||||||
|
# 3、设置_last_upper_hopper_weight 为当前重量
|
||||||
|
self._last_upper_hopper_weight = weight
|
||||||
|
|
||||||
# 上料斗方量设置
|
# 上料斗方量设置
|
||||||
def setUpperHopperVolume(self, volume: float):
|
def setUpperHopperVolume(self, volume: float):
|
||||||
"""Args:
|
"""Args:
|
||||||
@ -324,22 +361,65 @@ class HopperWidget(QWidget):
|
|||||||
"""
|
"""
|
||||||
self.upper_extra_label.setText(f"{volume}方(预估)")
|
self.upper_extra_label.setText(f"{volume}方(预估)")
|
||||||
|
|
||||||
|
# 上料斗夹爪开合角度设置
|
||||||
|
def setUpperHopperClampAngle(self, angle: float):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
angle: 传入多少角度(单位°)
|
||||||
|
"""
|
||||||
|
self.upper_clamp_widget.set_angle(angle)
|
||||||
|
|
||||||
# 下料斗重量设置
|
# 下料斗重量设置
|
||||||
def setLowerHopperWeight(self, weight:float):
|
def setLowerHopperWeight(self, weight:int):
|
||||||
|
# 仅当重量变化时,才更新标签和进度
|
||||||
|
if weight != self._last_lower_hopper_weight:
|
||||||
|
# 1、更新下料斗显示标签,显示的重量
|
||||||
self.lower_weight_label.setText(f"{weight}kg")
|
self.lower_weight_label.setText(f"{weight}kg")
|
||||||
|
|
||||||
# 下料斗开合角度设置
|
# 2、更新下料斗的进度显示
|
||||||
|
# 假设下料斗装满之后 总重量为 5100kg (褚工说设置为 6000kg 11/6)
|
||||||
|
total_weight = 6000
|
||||||
|
self._update_lower_inner_height(total_weight, weight)
|
||||||
|
|
||||||
|
# 3、设置_last_lower_hopper_weight 为当前重量
|
||||||
|
self._last_lower_hopper_weight = weight
|
||||||
|
|
||||||
|
def _update_lower_inner_height(self, total_weight, current_weight: float):
|
||||||
|
# 1、处理边界值
|
||||||
|
clamped_weight = max(0.0, min(current_weight, total_weight))
|
||||||
|
|
||||||
|
# 2、计算占比
|
||||||
|
weight_ratio = clamped_weight / (total_weight * 1.0)
|
||||||
|
|
||||||
|
# 3、根据占比计算当前的实际高度
|
||||||
|
inner_img_height = 100 # 内部料斗阴影的高度为100px
|
||||||
|
target_height = int(weight_ratio * inner_img_height)
|
||||||
|
|
||||||
|
# 4、设置内部阴影标签的高度
|
||||||
|
self.lower_inner_label.setFixedHeight(target_height)
|
||||||
|
|
||||||
|
# 5、计算标签位置
|
||||||
|
# container_bottom = self.lower_bg_widget.y() + self.lower_bg_widget.height()
|
||||||
|
container_bottom = 117 # 容器的高固定为了 117px (背景图片"料斗1"的高)
|
||||||
|
label_y = container_bottom - target_height - 8
|
||||||
|
self.lower_inner_label.move(14, label_y)
|
||||||
|
# print("_update_lower_inner_height", container_bottom)
|
||||||
|
|
||||||
|
# 6、强制刷新UI,确保立即显示变化
|
||||||
|
self.lower_inner_label.update()
|
||||||
|
|
||||||
|
# 下料斗开合角度设置 (包括 夹爪和标签)
|
||||||
def setLowerHopperOpeningAngle(self, angle: float):
|
def setLowerHopperOpeningAngle(self, angle: float):
|
||||||
"""Args:
|
"""Args:
|
||||||
angle : 传入多少度 (单位°)
|
angle : 传入多少度 (单位°)
|
||||||
"""
|
"""
|
||||||
self.lower_extra_label.setText(f"开: {angle}°")
|
self.lower_extra_label.setText(f"开: {angle}°") # 设置下料斗角度标签
|
||||||
self.lower_clamp_widget.set_angle(angle)
|
self.lower_clamp_widget.set_angle(angle) # 设置下料斗夹爪开合角度
|
||||||
|
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
# 设置上料斗状态(0=绿,1=黄,2=红)
|
# 设置上料斗状态(0=绿,1=黄,2=红)
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
def setUpperArchStatus(self, status: int):
|
def setUpperHopperStatus(self, status: int):
|
||||||
"""
|
"""
|
||||||
设置上料斗状态图片
|
设置上料斗状态图片
|
||||||
Args:
|
Args:
|
||||||
@ -361,17 +441,12 @@ class HopperWidget(QWidget):
|
|||||||
# 加载并缩放图片
|
# 加载并缩放图片
|
||||||
status_pixmap = QPixmap(img_path)
|
status_pixmap = QPixmap(img_path)
|
||||||
if not status_pixmap.isNull():
|
if not status_pixmap.isNull():
|
||||||
status_pixmap = status_pixmap.scaled(
|
|
||||||
22, 22,
|
|
||||||
Qt.KeepAspectRatio,
|
|
||||||
Qt.SmoothTransformation
|
|
||||||
)
|
|
||||||
self.upper_status_label.setPixmap(status_pixmap)
|
self.upper_status_label.setPixmap(status_pixmap)
|
||||||
|
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
# 设置下料斗状态(0=绿,1=黄,2=红)
|
# 设置下料斗状态(0=绿,1=黄,2=红)
|
||||||
# ------------------------------
|
# ------------------------------
|
||||||
def setLowerArchStatus(self, status: int):
|
def setLowerHopperStatus(self, status: int):
|
||||||
"""
|
"""
|
||||||
设置下料斗状态图片
|
设置下料斗状态图片
|
||||||
Args:
|
Args:
|
||||||
@ -390,11 +465,6 @@ class HopperWidget(QWidget):
|
|||||||
|
|
||||||
status_pixmap = QPixmap(img_path)
|
status_pixmap = QPixmap(img_path)
|
||||||
if not status_pixmap.isNull():
|
if not status_pixmap.isNull():
|
||||||
status_pixmap = status_pixmap.scaled(
|
|
||||||
22, 22,
|
|
||||||
Qt.KeepAspectRatio,
|
|
||||||
Qt.SmoothTransformation
|
|
||||||
)
|
|
||||||
self.lower_status_label.setPixmap(status_pixmap)
|
self.lower_status_label.setPixmap(status_pixmap)
|
||||||
|
|
||||||
# 隐藏上料斗 (用于上料斗移动)
|
# 隐藏上料斗 (用于上料斗移动)
|
||||||
@ -420,7 +490,7 @@ if __name__ == "__main__":
|
|||||||
window.setLowerHopperWeight(2000)
|
window.setLowerHopperWeight(2000)
|
||||||
window.setUpperHopperVolume(3.0)
|
window.setUpperHopperVolume(3.0)
|
||||||
window.setLowerHopperOpeningAngle(45)
|
window.setLowerHopperOpeningAngle(45)
|
||||||
window.setUpperArchStatus(2)
|
window.setUpperHopperStatus(2)
|
||||||
window.setLowerArchStatus(1)
|
window.setLowerHopperStatus(1)
|
||||||
window.show()
|
window.show()
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec())
|
||||||
@ -1,14 +1,88 @@
|
|||||||
from PySide6.QtWidgets import QWidget, QLabel, QHBoxLayout
|
from PySide6.QtWidgets import QWidget, QLabel, QHBoxLayout
|
||||||
from PySide6.QtGui import QPixmap, QFont
|
from PySide6.QtGui import QPixmap, QFont, QTransform
|
||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt, QPropertyAnimation, QEasingCurve, Property
|
||||||
|
|
||||||
import resources.resources_rc
|
import resources.resources_rc
|
||||||
from utils.image_paths import ImagePaths
|
from utils.image_paths import ImagePaths
|
||||||
|
|
||||||
|
class BladeLabel(QLabel):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._rotation = 0.0
|
||||||
|
self._original_pixmap = None
|
||||||
|
self._original_center_x = 0.0 # 原始图片自身中心点x
|
||||||
|
self._original_center_y = 0.0 # 原始图片自身中心点y
|
||||||
|
self._fixed_center_in_parent_x = 0 # 父容器中的固定中心点x(关键)
|
||||||
|
self._fixed_center_in_parent_y = 0 # 父容器中的固定中心点y(关键)
|
||||||
|
# self.setFixedSize(50, 54)
|
||||||
|
|
||||||
|
def set_original_pixmap(self, pixmap, fixed_center_x, fixed_center_y):
|
||||||
|
"""
|
||||||
|
:param pixmap: 原始图片
|
||||||
|
:param fixed_center_x: 父容器中固定的中心点x坐标(绝对位置)
|
||||||
|
:param fixed_center_y: 父容器中固定的中心点y坐标(绝对位置)
|
||||||
|
"""
|
||||||
|
self._original_pixmap = pixmap
|
||||||
|
if pixmap.isNull():
|
||||||
|
print("错误:搅拌桨图片加载失败!")
|
||||||
|
return
|
||||||
|
# 记录原始图片自身的中心点(用于旋转计算)
|
||||||
|
self._original_center_x = 28 # 图片的中心点为 28,28
|
||||||
|
self._original_center_y = 28
|
||||||
|
# 记录在父容器中的固定中心点(旋转时始终对齐这个点)
|
||||||
|
self._fixed_center_in_parent_x = fixed_center_x
|
||||||
|
self._fixed_center_in_parent_y = fixed_center_y
|
||||||
|
# 初始显示图片
|
||||||
|
self.setPixmap(pixmap)
|
||||||
|
# 初始位置:让原始图片的中心点与固定中心点对齐
|
||||||
|
self._update_position(pixmap.width(), pixmap.height())
|
||||||
|
|
||||||
|
def _update_position(self, current_w, current_h):
|
||||||
|
"""根据当前图片尺寸,计算位置使中心点与固定坐标对齐"""
|
||||||
|
# 当前图片的中心点坐标(自身坐标系)
|
||||||
|
current_center_x = current_w / 2
|
||||||
|
current_center_y = current_h / 2
|
||||||
|
# 计算左上角坐标:固定中心点 - 当前图片中心点
|
||||||
|
x = self._fixed_center_in_parent_x - current_center_x
|
||||||
|
y = self._fixed_center_in_parent_y - current_center_y
|
||||||
|
self.move(round(x), round(y)) # 取整避免浮点数位置偏差
|
||||||
|
self.setFixedSize(current_w, current_h)
|
||||||
|
|
||||||
|
def get_rotation(self):
|
||||||
|
return self._rotation
|
||||||
|
|
||||||
|
def set_rotation(self, angle):
|
||||||
|
self._rotation = angle
|
||||||
|
if self._original_pixmap is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 生成旋转后的图片(保持旋转中心为原始图片中心)
|
||||||
|
transform = QTransform()
|
||||||
|
transform.translate(self._original_center_x, self._original_center_y)
|
||||||
|
transform.rotate(angle)
|
||||||
|
transform.translate(-self._original_center_x, -self._original_center_y)
|
||||||
|
rotated_pixmap = self._original_pixmap.transformed(transform, Qt.SmoothTransformation)
|
||||||
|
|
||||||
|
# 强制对齐固定中心点(关键:无论尺寸如何变化,中心点不变)
|
||||||
|
self._update_position(rotated_pixmap.width(), rotated_pixmap.height())
|
||||||
|
self.setPixmap(rotated_pixmap)
|
||||||
|
|
||||||
|
rotation = Property(float, get_rotation, set_rotation)
|
||||||
|
|
||||||
|
def reset_to_original(self):
|
||||||
|
self._rotation = 0.0 # 重置旋转角度为0°
|
||||||
|
if self._original_pixmap is not None:
|
||||||
|
self.setPixmap(self._original_pixmap) # 恢复原始图片
|
||||||
|
# 恢复初始位置(基于原始图片尺寸)
|
||||||
|
self._update_position(self._original_pixmap.width(), self._original_pixmap.height())
|
||||||
|
|
||||||
class MixerWidget(QWidget):
|
class MixerWidget(QWidget):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
|
# 两个搅拌桨的转动的动画引用
|
||||||
|
self.animations = [] # 保存动画引用
|
||||||
|
|
||||||
# 初始化布局
|
# 初始化布局
|
||||||
layout = QHBoxLayout(self)
|
layout = QHBoxLayout(self)
|
||||||
layout.setContentsMargins(0, 0, 0, 0)
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
@ -28,35 +102,71 @@ class MixerWidget(QWidget):
|
|||||||
""")
|
""")
|
||||||
layout.addWidget(self.text_label, alignment=Qt.AlignLeft)
|
layout.addWidget(self.text_label, alignment=Qt.AlignLeft)
|
||||||
|
|
||||||
# 2. 创建搅拌机设备及搅拌桨图标
|
# 2. 创建搅拌机设备
|
||||||
self.device_label = QLabel()
|
self.device_label = QLabel()
|
||||||
device_pixmap = QPixmap(ImagePaths.MIXER)
|
device_pixmap = QPixmap(ImagePaths.MIXER)
|
||||||
self.device_label.setPixmap(device_pixmap)
|
self.device_label.setPixmap(device_pixmap)
|
||||||
layout.addWidget(self.device_label, alignment=Qt.AlignLeft)
|
layout.addWidget(self.device_label, alignment=Qt.AlignLeft)
|
||||||
|
|
||||||
# 3. 叠加两个搅拌桨图标
|
# 3. 初始化两个搅拌桨
|
||||||
self.blade1 = QLabel(self.device_label) # 从左往右第一个搅拌桨
|
self._init_blades()
|
||||||
blade1_pixmap = QPixmap(ImagePaths.MIXER_PADDLE)
|
|
||||||
self.blade1.setPixmap(blade1_pixmap)
|
|
||||||
self.blade1.move(
|
|
||||||
(device_pixmap.width() - blade1_pixmap.width()) // 2 - 26,
|
|
||||||
(device_pixmap.height() - blade1_pixmap.height()) // 2 - 4
|
|
||||||
)
|
|
||||||
|
|
||||||
self.blade2 = QLabel(self.device_label)
|
def _init_blades(self):
|
||||||
blade2_pixmap = QPixmap(ImagePaths.MIXER_PADDLE) # 从左往右第二个搅拌桨
|
blade_pixmap = QPixmap(ImagePaths.MIXER_PADDLE)
|
||||||
self.blade2.setPixmap(blade2_pixmap)
|
if blade_pixmap.isNull():
|
||||||
self.blade2.move(
|
return
|
||||||
(device_pixmap.width() - blade2_pixmap.width()) // 2 + 31,
|
|
||||||
(device_pixmap.height() - blade2_pixmap.height()) // 2 - 4
|
|
||||||
)
|
|
||||||
|
|
||||||
# 测试代码
|
# 设备背景的尺寸(用于计算固定中心点)
|
||||||
if __name__ == "__main__":
|
device_pixmap = self.device_label.pixmap()
|
||||||
import sys
|
if not device_pixmap:
|
||||||
from PySide6.QtWidgets import QApplication, QMainWindow
|
return
|
||||||
|
device_w = device_pixmap.width()
|
||||||
|
device_h = device_pixmap.height()
|
||||||
|
|
||||||
app = QApplication(sys.argv)
|
# --------------------------
|
||||||
mixer_widget = MixerWidget()
|
# 左搅拌桨:计算固定中心点
|
||||||
mixer_widget.show()
|
# --------------------------
|
||||||
sys.exit(app.exec())
|
left_center_x = (device_w // 2) - 26 # 左桨中心点x(示例值,需根据实际调整)
|
||||||
|
left_center_y = device_h // 2 - 5 # 左桨中心点y(示例值,需根据实际调整)
|
||||||
|
self.blade1 = BladeLabel(self.device_label)
|
||||||
|
self.blade1.set_original_pixmap(blade_pixmap, left_center_x, left_center_y)
|
||||||
|
|
||||||
|
# --------------------------
|
||||||
|
# 右搅拌桨:计算固定中心点
|
||||||
|
# --------------------------
|
||||||
|
right_center_x = (device_w // 2) + 30 # 右桨中心点x(示例值,需根据实际调整)
|
||||||
|
right_center_y = device_h // 2 - 5 # 右桨中心点y(与左桨对齐)
|
||||||
|
self.blade2 = BladeLabel(self.device_label)
|
||||||
|
self.blade2.set_original_pixmap(blade_pixmap, right_center_x, right_center_y)
|
||||||
|
|
||||||
|
def _start_animation(self, blade: BladeLabel, duration: int, reverse: bool = False):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
blade: 所需旋转的搅拌桨标签
|
||||||
|
duration 一次搅拌桨旋转所需的时间,值越小,旋转越快
|
||||||
|
reverse: 是否反转(逆时针转)
|
||||||
|
"""
|
||||||
|
animation = QPropertyAnimation(blade, b"rotation")
|
||||||
|
animation.setStartValue(360 if reverse else 0)
|
||||||
|
animation.setEndValue(0 if reverse else 360)
|
||||||
|
animation.setDuration(duration)
|
||||||
|
animation.setEasingCurve(QEasingCurve.Linear)
|
||||||
|
animation.setLoopCount(-1)
|
||||||
|
self.animations.append(animation)
|
||||||
|
animation.start()
|
||||||
|
|
||||||
|
# 搅拌桨开始搅拌
|
||||||
|
def startBladeMix(self, duration=700):
|
||||||
|
self.animations.clear()
|
||||||
|
# 备注:duration控制搅拌桨旋转的速度,值越小旋转得越快
|
||||||
|
self._start_animation(self.blade1, duration)
|
||||||
|
self._start_animation(self.blade2, duration)
|
||||||
|
|
||||||
|
def stopBladeMix(self):
|
||||||
|
for animation in self.animations:
|
||||||
|
animation.stop()
|
||||||
|
|
||||||
|
if self.blade1:
|
||||||
|
self.blade1.reset_to_original()
|
||||||
|
if self.blade2:
|
||||||
|
self.blade2.reset_to_original()
|
||||||
|
|||||||
315
view/widgets/segment_details_dialog.py
Normal 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())
|
||||||
@ -60,10 +60,10 @@ class SystemCenterDialog(QDialog):
|
|||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.init_ui()
|
self._init_ui()
|
||||||
self.init_animations() # 初始化动画
|
self.init_animations() # 初始化动画
|
||||||
|
|
||||||
def init_ui(self):
|
def _init_ui(self):
|
||||||
# 弹窗基础设置
|
# 弹窗基础设置
|
||||||
self.setWindowTitle("系统中心")
|
self.setWindowTitle("系统中心")
|
||||||
self.setWindowFlags(Qt.FramelessWindowHint) # 隐藏默认边框
|
self.setWindowFlags(Qt.FramelessWindowHint) # 隐藏默认边框
|
||||||
|
|||||||
385
view/widgets/system_diagnostics_dialog.py
Normal 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())
|
||||||
@ -5,6 +5,8 @@ from PySide6.QtCore import Qt, QTimer, QDateTime
|
|||||||
import resources.resources_rc
|
import resources.resources_rc
|
||||||
from utils.image_paths import ImagePaths
|
from utils.image_paths import ImagePaths
|
||||||
|
|
||||||
|
"""主界面最上方的导航栏"""
|
||||||
|
|
||||||
# 自定义消息容器, 显示系统消息
|
# 自定义消息容器, 显示系统消息
|
||||||
class MsgContainer(QWidget):
|
class MsgContainer(QWidget):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
|
|||||||
@ -1,14 +1,19 @@
|
|||||||
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||||
QPushButton, QMessageBox, QApplication)
|
QPushButton, QMessageBox, QApplication)
|
||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt, Signal
|
||||||
from PySide6.QtGui import QPainter, QPixmap, QFont
|
from PySide6.QtGui import QPainter, QPixmap, QFont
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import resources.resources_rc
|
import resources.resources_rc
|
||||||
from utils.image_paths import ImagePaths
|
from utils.image_paths import ImagePaths
|
||||||
|
|
||||||
# 任务控件,如:管片任务、派单任务
|
"""
|
||||||
|
任务控件,如:管片任务、派单任务
|
||||||
|
"""
|
||||||
class TaskWidget(QWidget):
|
class TaskWidget(QWidget):
|
||||||
|
# 任务详情信号: task1表示第一条任务
|
||||||
|
task_details_signal = Signal(str)
|
||||||
|
|
||||||
def __init__(self, taskTitle:str, parent=None):
|
def __init__(self, taskTitle:str, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
# 设置Widget大小与背景图一致
|
# 设置Widget大小与背景图一致
|
||||||
@ -153,8 +158,12 @@ class TaskWidget(QWidget):
|
|||||||
|
|
||||||
def _show_detail_dialog(self, task_name):
|
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)
|
||||||
|
|
||||||
# --------------------------
|
# --------------------------
|
||||||
# 对外接口:修改任务属性
|
# 对外接口:修改任务属性
|
||||||
@ -178,6 +187,33 @@ class TaskWidget(QWidget):
|
|||||||
task_id_label = self.task_controls[task_name]["task_id_label"]
|
task_id_label = self.task_controls[task_name]["task_id_label"]
|
||||||
task_id_label.setText(new_id)
|
task_id_label.setText(new_id)
|
||||||
|
|
||||||
|
def get_task_volume(self, task_name:str):
|
||||||
|
"""
|
||||||
|
获取指定任务的方量, 传入任务名,如 task1、task2、task3
|
||||||
|
return: 返回 float类型一位小数的方量值
|
||||||
|
"""
|
||||||
|
if task_name in self.task_controls:
|
||||||
|
volume_label = self.task_controls[task_name]["volume_label"]
|
||||||
|
|
||||||
|
# 提取 volume_label中显示的 "方量 200" 中的数字部分
|
||||||
|
# 1. 去除前后空格,按空格分割字符串
|
||||||
|
volume_text = volume_label.text().strip()
|
||||||
|
parts = volume_text.split()
|
||||||
|
|
||||||
|
# 2. 取分割后的数字部分
|
||||||
|
if len(parts) >= 2:
|
||||||
|
number_str = parts[1] # 得到 "200"
|
||||||
|
else:
|
||||||
|
# 格式异常(没有数字部分),返回None
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 褚工说任务中显示的方量只有一位小数
|
||||||
|
try:
|
||||||
|
volume_value = round(float(number_str), 1)
|
||||||
|
return volume_value
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
widget = TaskWidget("管片任务")
|
widget = TaskWidget("管片任务")
|
||||||
|
|||||||
@ -4,13 +4,36 @@ from PySide6.QtCore import Qt
|
|||||||
from PySide6.QtGui import QDoubleValidator
|
from PySide6.QtGui import QDoubleValidator
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# 调整计划方量
|
"""
|
||||||
|
调整计划方量, 左侧减按钮, 右侧加按钮
|
||||||
|
这里的 最小值、最大值、初始值 需要读取配置文件来决定
|
||||||
|
"""
|
||||||
|
|
||||||
|
class CustomLineEdit(QLineEdit):
|
||||||
|
def __init__(self, default_text: str, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.default_text = default_text # 保存初始化时的默认文本
|
||||||
|
self.setText(self.default_text) # 初始化为默认文本
|
||||||
|
|
||||||
|
def focusOutEvent(self, event):
|
||||||
|
super().focusOutEvent(event) # 先执行父类的焦点离开逻辑
|
||||||
|
|
||||||
|
# 检查文本是否为空(或仅含空格)
|
||||||
|
current_text = self.text().strip()
|
||||||
|
if not current_text:
|
||||||
|
self.setText(self.default_text) # 为空则恢复默认值
|
||||||
|
else: # 不为空,显示一位小数
|
||||||
|
value = round(float(current_text), 1)
|
||||||
|
self.setText(f"{value:.1f}")
|
||||||
|
|
||||||
|
self.setCursorPosition(0) # 光标移到最前面 (保证数值显示完整)
|
||||||
|
|
||||||
class ValueAdjuster(QWidget):
|
class ValueAdjuster(QWidget):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.min_value = 0 # 最小值
|
self.min_value = 0.0 # 最小值
|
||||||
self.max_value = 99 # 最大值
|
self.max_value = 99.0 # 最大值
|
||||||
self.value = 2.5 # 初始值
|
self.value = 2.5 # 初始值 (需要显示一位数字)
|
||||||
|
|
||||||
self.setFixedSize(102, 32)
|
self.setFixedSize(102, 32)
|
||||||
|
|
||||||
@ -21,7 +44,9 @@ class ValueAdjuster(QWidget):
|
|||||||
self.minus_btn.setCursor(Qt.PointingHandCursor)
|
self.minus_btn.setCursor(Qt.PointingHandCursor)
|
||||||
|
|
||||||
# 中间的编辑栏
|
# 中间的编辑栏
|
||||||
self.line_edit = QLineEdit(f"{self.value:.1f}") # 显示1位小数
|
# 支持显示两位小数
|
||||||
|
# self.line_edit = QLineEdit(f"{self.value:.1f}") # 显示1位小数
|
||||||
|
self.line_edit = CustomLineEdit(f"{self.value:.1f}") # 显示1位小数
|
||||||
self.line_edit.setFixedSize(40, 26)
|
self.line_edit.setFixedSize(40, 26)
|
||||||
|
|
||||||
# 加号按钮
|
# 加号按钮
|
||||||
@ -31,8 +56,8 @@ class ValueAdjuster(QWidget):
|
|||||||
|
|
||||||
# 配置QLineEdit:支持数字输入+居中显示
|
# 配置QLineEdit:支持数字输入+居中显示
|
||||||
self.line_edit.setAlignment(Qt.AlignCenter) # 文本居中
|
self.line_edit.setAlignment(Qt.AlignCenter) # 文本居中
|
||||||
# 限制输入为浮点数(支持负数,范围可自定义)
|
# 限制输入为浮点数(范围可自定义)
|
||||||
self.line_edit.setValidator(QDoubleValidator(0, 99, 1, self)) # 最多1位小数
|
self.line_edit.setValidator(QDoubleValidator(self.min_value, self.max_value, 1, self)) # 最多1位小数 (必选)
|
||||||
self.line_edit.textChanged.connect(self.on_text_changed) # 监听输入变化
|
self.line_edit.textChanged.connect(self.on_text_changed) # 监听输入变化
|
||||||
|
|
||||||
# 设置样式表(保持与按钮风格统一)
|
# 设置样式表(保持与按钮风格统一)
|
||||||
@ -112,7 +137,7 @@ class ValueAdjuster(QWidget):
|
|||||||
self.line_edit.setText(f"{self.value:.1f}")
|
self.line_edit.setText(f"{self.value:.1f}")
|
||||||
|
|
||||||
def on_text_changed(self, text):
|
def on_text_changed(self, text):
|
||||||
"""监听输入框文本变化,更新内部value"""
|
"""监听输入框文本变化, 更新内部value"""
|
||||||
if not text:
|
if not text:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@ -126,7 +151,7 @@ class ValueAdjuster(QWidget):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 获取具体的方量数值
|
# 获取具体的方量数值,float类型 (一位小数)
|
||||||
def get_value(self):
|
def get_value(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|||||||