This commit is contained in:
2025-11-16 11:58:22 +08:00
commit ccc794d158
25 changed files with 924 additions and 0 deletions

3
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# 默认忽略的文件
/shelf/
/workspace.xml

View File

@ -0,0 +1,18 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="5">
<item index="0" class="java.lang.String" itemvalue="scipy" />
<item index="1" class="java.lang.String" itemvalue="numpy" />
<item index="2" class="java.lang.String" itemvalue="snap7" />
<item index="3" class="java.lang.String" itemvalue="jsonchema" />
<item index="4" class="java.lang.String" itemvalue="werkzeung" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="pyside6" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="pyside6" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/中交三航背景界面.iml" filepath="$PROJECT_DIR$/.idea/中交三航背景界面.iml" />
</modules>
</component>
</project>

8
.idea/中交三航背景界面.iml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="pyside6" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

41
bottom_control_widget.py Normal file
View File

@ -0,0 +1,41 @@
from PySide6.QtWidgets import (QWidget, QHBoxLayout, QLabel, QPushButton,
QMessageBox, QSpacerItem, QSizePolicy)
from PySide6.QtGui import QPixmap, QPainter, QFont, QPen
from PySide6.QtCore import Qt, QDateTime, QEvent, QSize
class BottomControlWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.initUI()
def initUI(self):
# 1. 加载背景图并设置控件尺寸
bg_path = "底部背景.png"
self.bg_pixmap = QPixmap(bg_path)
if self.bg_pixmap.isNull():
print("警告:底部背景.png 加载失败,请检查路径!")
self.setFixedSize(1280, 66)
else:
self.setFixedSize(self.bg_pixmap.size())
# 2. 主布局水平布局组件间距6px
main_layout = QHBoxLayout(self)
main_layout.setContentsMargins(10, 10, 10, 10) # 适当留白避免贴边
main_layout.setSpacing(6)
def paintEvent(self, event):
"""绘制背景图"""
super().paintEvent(event)
if not self.bg_pixmap.isNull():
painter = QPainter(self)
painter.drawPixmap(0, 0, self.bg_pixmap)
if __name__ == "__main__":
import sys
from PySide6.QtWidgets import QApplication
app = QApplication(sys.argv)
widget = BottomControlWidget()
widget.show()
sys.exit(app.exec())

4
config/config.json Normal file
View File

@ -0,0 +1,4 @@
{
"hopper_up_weight": 1111,
"hopper_down_weight": 6524
}

157
config/tcp_server.py Normal file
View File

@ -0,0 +1,157 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/9/19 09:48
# @Author : reenrr
# @File : mock_server.py
'''
import socket
import json
import threading
import time
from datetime import datetime
import os
class TCPServerSimulator:
def __init__(self, host='127.0.0.1', port=8888, config_file='config.json'):
self.host = host
self.port = port
self.server_socket = None
self.is_running = False
self.client_sockets = []
self.config_file = config_file
# 初始状态为None
self.data_template = None
# 从配置文件中加载固定数据
self.load_config_data()
# 模拟数据模板
if self.data_template is None:
self.data_template = {
"hopper_up_weight": 0.0, # 上料斗重量
"hopper_down_weight": 0.0 # 下料斗重量
}
def load_config_data(self):
"""从配置文件中加载固定数据"""
try:
if os.path.exists(self.config_file):
with open(self.config_file, 'r', encoding='utf-8') as f:
self.data_template = json.load(f)
print(f"成功从 {self.config_file} 加载配置数据")
else:
print(f"配置文件 {self.config_file} 不存在")
except Exception as e:
print(f"加载配置文件时发生错误:{e},将使用默认数据")
self.data_template = None
def start(self):
"""启动服务器"""
self.is_running = True
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.bind((self.host, self.port))
self.server_socket.listen(5)
print(f"服务器已启动,监听 {self.host}:{self.port}...")
# 启动接受连接的线程
accept_thread = threading.Thread(target=self.accept_connections, daemon=True)
accept_thread.start()
try:
while self.is_running:
time.sleep(1)
except KeyboardInterrupt:
print("\n服务器正在关闭...")
self.stop()
def accept_connections(self):
"""接受客户端连接"""
while self.is_running:
try:
client_socket, client_address = self.server_socket.accept()
self.client_sockets.append(client_socket)
print(f"客户端 {client_address} 已连接")
# 发送数据
data = self.generate_simulated_data()
self.send_data(client_socket, data)
print(f"已向客户端 {client_address} 发送数据:{data}")
# 启动一个线程监听客户端发送的指令
threading.Thread(
target=self.listen_client_commands,
args=(client_socket,client_address),
daemon=True
).start()
except Exception as e:
if self.is_running:
print(f"接受连接时发生错误: {e}")
break
def listen_client_commands(self, client_socket, client_address):
"""监听客户端发送的指令"""
while self.is_running and client_socket in self.client_sockets:
try:
# 接收客户端发送的指令
data = client_socket.recv(1024).decode('utf-8').strip()
if data:
print(f"客户端 {client_address} 发送指令: {data}")
else:
print(f"客户端 {client_address} 已断开连接")
self.client_sockets.remove(client_socket)
client_socket.close()
break
except Exception as e:
print(f"监听客户端 {client_address} 指令时发生错误: {e}")
self.client_sockets.remove(client_socket)
client_socket.close()
break
def generate_simulated_data(self):
"""生成模拟的状态数据"""
if self.data_template is None:
return None
data = self.data_template.copy()
data["timestamp"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
return data
def send_data(self, client_socket, data):
"""向客户端发送数据"""
try:
# 转换为JSON字符串并添加换行符作为结束标记
if data is None:
data_str = json.dumps(None) + "\n"
else:
data_str = json.dumps(data) + "\n"
client_socket.sendall(data_str.encode('utf-8'))
except Exception as e:
print(f"向客户端 {client_socket.getpeername()} 发送数据时发生错误: {e}")
def stop(self):
"""停止服务器"""
self.is_running = False
# 关闭所有客户端连接
for sock in self.client_sockets:
try:
sock.close()
except Exception as e:
print(f"关闭客户端连接时发生错误: {e}")
# 关闭服务器套接字
if self.server_socket:
try:
self.server_socket.close()
except Exception as e:
print(f"关闭服务器套接字时发生错误: {e}")
print("服务器已关闭")
if __name__ == '__main__':
server = TCPServerSimulator(host='127.0.0.1', port=8888)
server.start()

129
main_window.py Normal file
View File

@ -0,0 +1,129 @@
# coding: utf-8
from typing import List
from PySide6.QtCore import Qt, Signal, QEasingCurve, QUrl, QSize, QTimer, QEvent
from PySide6.QtGui import QIcon, QDesktopServices, QColor, QPalette, QBrush, QImage
from PySide6.QtWidgets import (QApplication, QHBoxLayout, QVBoxLayout,
QFrame, QWidget, QSpacerItem, QSizePolicy, QMainWindow, QPushButton)
from system_nav_bar import SystemNavBar
from bottom_control_widget import BottomControlWidget
from weight_dialog import WeightDetailsDialog
from tcp_client import TcpClient
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initWindow()
self.createSubWidgets() # 创建子部件
self.setupLayout() # 设置布局
self.tcp_client = TcpClient(self) # 创建 TCP 客户端
self.bind_signals() # 绑定信号槽
def initWindow(self):
"""初始化窗口基本属性"""
self.setWindowTitle("中交三航主界面") # 设置窗口标题
# 触摸屏尺寸为 1280*800
self.setMinimumSize(1280, 800)
self.setObjectName("MainWindow")
# Qt.FramelessWindowHint
self.setWindowFlags(Qt.FramelessWindowHint) # 无边框窗口
# 设置主界面背景图片
try:
self.background_image = QImage("主界面背景.png")
if self.background_image.isNull():
raise Exception("图片为空,可能路径错误或格式不支持")
# print("图片加载成功")
except Exception as e:
print(f"主界面背景图片加载失败: {e}")
self.background_image = None
return # 加载背景失败
self.update_background()
def createSubWidgets(self):
"""创建所有子部件实例"""
self.system_nav_bar = SystemNavBar() # 最上方:系统导航栏
self.bottom_control_widget = BottomControlWidget() # 最下方: 控制的按钮 (系统诊断、系统中心等)
# 上料斗、下料斗重量显示
self.weight_dialog1 = WeightDetailsDialog(self, title="上料斗重量")
self.weight_dialog2 = WeightDetailsDialog(self, title="下料斗重量")
def setupLayout(self):
"""设置垂直布局,从上到下排列部件"""
main_layout = QVBoxLayout(self) # 主布局:垂直布局
main_layout.setSpacing(0) # 部件间距0px
main_layout.setContentsMargins(0, 0, 0, 0) # 左上右下边距0px
# 添加最上方的 系统导航栏包括系统标题、中交三航的logo等
main_layout.addWidget(self.system_nav_bar, alignment=Qt.AlignTop)
# 添加中间内容区
main_layout.addWidget(self.weight_dialog1, alignment=Qt.AlignCenter)
main_layout.addWidget(self.weight_dialog2, alignment=Qt.AlignCenter)
# 关闭按钮
self.system_nav_bar.msg_container.close_btn.clicked.connect(self.close)
# 将布局应用到主窗口
self.setLayout(main_layout)
def update_background(self):
"""更新主界面背景图片"""
if self.background_image and not self.background_image.isNull():
palette = self.palette()
# 按当前窗口尺寸缩放图片
scaled_image = self.background_image.scaled(
self.size(),
Qt.IgnoreAspectRatio,
Qt.SmoothTransformation
)
palette.setBrush(QPalette.Window, QBrush(scaled_image))
self.setPalette(palette)
self.setAutoFillBackground(True)
def resizeEvent(self, e):
"""窗口大小变化时的回调"""
super().resizeEvent(e)
self.update_background() # 重新加载背景图片
def keyPressEvent(self, event):
if event.key() == Qt.Key_Escape: # 按下Esc键, 退出界面
self.close()
super().keyPressEvent(event)
# ---------------
# TCP更新重量信息
# ---------------
def bind_signals(self):
"""绑定TCP信号到弹窗更新函数"""
self.tcp_client.weight_updated.connect(self.update_weight_dialogs) # 绑定更新重量信息的信号槽
# 连接状态信号:更新按钮状态和弹窗图标
self.tcp_client.connection_status_changed.connect(self.update_connection_status)
# 启动 TCP 连接
print(f"客户端启动,自动连接服务端{self.tcp_client.tcp_server_host}:{self.tcp_client.tcp_server_port}...")
self.tcp_client._connect_to_server()
def update_connection_status(self, is_connected):
"""更新连接状态"""
self.weight_dialog1._update_status_icon(is_connected)
self.weight_dialog2._update_status_icon(is_connected)
def update_weight_dialogs(self, data):
"""更新上料斗、下料斗重量显示"""
# 上料斗重量(对应字段 hopper_up_weight,需与服务器一致)
up_weight = data.get("hopper_up_weight", 0)
self.weight_dialog1.update_weight(up_weight)
# 下料斗重量(对应字段 hopper_down_weight,需与服务器一致)
down_weight = data.get("hopper_down_weight", 0)
self.weight_dialog2.update_weight(down_weight)
if __name__ == "__main__":
import sys
app = QApplication([])
window = MainWindow()
# window.show() # 显示主界面
window.showFullScreen() # 全屏显示主界面
sys.exit(app.exec())

141
system_nav_bar.py Normal file
View File

@ -0,0 +1,141 @@
from PySide6.QtWidgets import (QWidget, QLabel, QHBoxLayout,
QSpacerItem, QSizePolicy, QPushButton)
from PySide6.QtGui import QPixmap, QFont, QPainter, QIcon
from PySide6.QtCore import Qt, QTimer, QDateTime
# 自定义消息容器, 显示系统消息
class MsgContainer(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setFixedSize(770, 24)
# 加载消息区域背景图
self.bg_pixmap = QPixmap("系统消息背景.png") # 替换为实际路径
if self.bg_pixmap.isNull():
print("警告:系统消息背景.png 加载失败")
# 消息区域内部布局(喇叭+文本)
msg_layout = QHBoxLayout(self)
msg_layout.setContentsMargins(0, 0, 0, 0) # 调整内边距,避免内容贴边
msg_layout.setSpacing(3) # 喇叭和文本的间距
# 消息喇叭图标
self.msg_icon = QLabel()
self.msg_icon.setFixedSize(13, 18)
# self.msg_icon.setStyleSheet("background-color:red;")
self.msg_icon.setPixmap(QPixmap("系统消息喇叭.png")) # 替换为实际路径
msg_layout.addWidget(self.msg_icon, alignment=Qt.AlignVCenter | Qt.AlignLeft)
# 消息文本
current_time = QDateTime.currentDateTime().toString("hh:mm:ss")
self.msg_text = QLabel(f"{current_time} 开始启动智能浇筑系统")
self.msg_text.setFixedWidth(740)
# self.msg_text.setStyleSheet("color: white; font-size: 14px;background-color:red;") # 文本样式
self.msg_text.setStyleSheet("color: white; font-size: 16px;font-weight:Bold;")
self.msg_text.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
msg_layout.addWidget(self.msg_text)
# 关闭按钮
self._create_close_button(msg_layout)
def _create_close_button(self, parent_layout):
self.close_btn = QPushButton()
self.close_btn.setFixedSize(20, 20)
close_icon = QPixmap("关闭图标.png")
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;
}
"""
)
parent_layout.addWidget(self.close_btn)
def paintEvent(self, event):
# 绘制消息区域背景图
super().paintEvent(event) # 确保子控件正常绘制
if self.bg_pixmap.isNull():
return # 图片加载失败则不绘制
painter = QPainter(self)
painter.setRenderHint(QPainter.SmoothPixmapTransform) # 缩放平滑
painter.drawPixmap(self.rect(), self.bg_pixmap)
class SystemNavBar(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
# 设置尺寸
self.setFixedSize(1280, 80)
# 1. 加载背景图
self.bg_pixmap = QPixmap("系统主界面导航栏.png") # 替换为实际图片路径
if self.bg_pixmap.isNull():
print("警告:背景图加载失败,请检查路径!")
main_layout = QHBoxLayout(self)
main_layout.setContentsMargins(9, 9, 9, 19)
main_layout.setSpacing(100) # 注意左侧的logo+系统标题的容器 和 系统消息的间隔
# 左侧区域logo + 系统标题
left_container = QWidget()
left_container.setFixedSize(400, 53)
left_layout = QHBoxLayout(left_container) # 容器内部的水平布局
left_layout.setContentsMargins(0, 0, 0, 0) # 容器内边距
left_layout.setSpacing(6) # 设置logo和标题之间的间隙为6px
# 系统logo
self.logo = QLabel()
self.logo.setFixedSize(53, 53)
self.logo.setPixmap(QPixmap("系统logo.png"))
left_layout.addWidget(self.logo, alignment=Qt.AlignTop)
# 系统总标题
self.title = QLabel()
self.title.setPixmap(QPixmap("系统总标题.png"))
left_layout.addWidget(self.title, alignment=Qt.AlignCenter)
main_layout.addWidget(left_container, alignment=Qt.AlignTop)
# 中间区域:系统消息(喇叭+文本+背景)
self.msg_container = MsgContainer()
main_layout.addWidget(self.msg_container, alignment=Qt.AlignBottom | Qt.AlignRight)
# 右侧区域:实时时间
self.time_label = QLabel()
self.time_label.setStyleSheet("color: white; font-size: 16px;font-weight:Bold;")
main_layout.addWidget(self.time_label, alignment= Qt.AlignTop | Qt.AlignRight)
# 启动时间更新定时器
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_time)
self.timer.start(1000) # 每秒更新一次
def paintEvent(self, event):
super().paintEvent(event)
painter = QPainter(self)
painter.setRenderHint(QPainter.SmoothPixmapTransform) # 缩放时平滑
painter.drawPixmap(0, 0, self.bg_pixmap)
def update_time(self):
current_time = QDateTime.currentDateTime().toString("yyyy/MM/dd hh:mm:ss")
self.time_label.setText(current_time)
# 测试代码
if __name__ == "__main__":
import sys
from PySide6.QtWidgets import QApplication, QMainWindow
app = QApplication(sys.argv)
nav_bar = SystemNavBar()
nav_bar.show()
sys.exit(app.exec())

174
tcp_client.py Normal file
View File

@ -0,0 +1,174 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/11/13 11:05
# @Author : reenrr
# @File : tcp_client.py
'''
import json
from PySide6.QtCore import QTimer, Slot, Signal, QObject
from PySide6.QtNetwork import QTcpSocket, QAbstractSocket
from datetime import datetime
# -----------
# 参数配置
# -----------
tcp_server_host = "127.0.0.1"
tcp_server_port = 8888
RECONNECT_INTERVAL = 2000 # 重连间隔(毫秒)
class TcpClient(QObject):
# 信号,用于向主窗口/重量弹窗发送最新重量数据
weight_updated = Signal(dict) # 重量更新信号
connection_status_changed = Signal(bool) # 连接状态更新信号
def __init__(self, parent=None):
super().__init__(parent)
self.is_running = False # 系统运行状态标记
self.latest_json_data = {} # 缓存服务端发送的最新JSON数据
self.statusWidgets = [] # 存储api_field + 对应的value标签
# ---------------
# TCP客户端核心配置
# ---------------
self.tcp_socket = QTcpSocket(self) # TCP socket实例
self.tcp_server_host = tcp_server_host
self.tcp_server_port = tcp_server_port
self.is_tcp_connected = False # TCP连接状态标记
self.has_connected_once = False # 连接至服务器至少一次标记(区别首次连接和断开后重连)
self.reconnect_count = 0 # 重连次数计数器
# 重连定时器每隔RECONNECT_INTERVAL毫秒重连一次
self.reconnect_timer = QTimer(self)
self.reconnect_timer.setInterval(RECONNECT_INTERVAL)
self.reconnect_timer.timeout.connect(self._reconnect_to_server) # 绑定重连函数
# 绑定TCP信号与槽事件驱动
self._bind_tcp_signals()
# ---------------------
# TCP连接相关函数
# ---------------------
def _connect_to_server(self):
"""主动发起连接(仅在未连接状态下有效"""
if not self.is_tcp_connected:
self.tcp_socket.abort() # 终止现有连接
self.tcp_socket.connectToHost(self.tcp_server_host, self.tcp_server_port)
print(f"发起连接请求:{self.tcp_server_host}:{self.tcp_server_port}")
def _reconnect_to_server(self):
"""重连执行函数:仅在未连接且未达最大次数时触发"""
if not self.is_tcp_connected:
self.reconnect_count += 1
# 日志优化每10次重连提示一次避免日志刷屏
if self.reconnect_count % 10 == 0:
print(f"已连续重连{self.reconnect_count}次,仍未连接服务端,请检查服务端状态...")
else:
print(f"{self.reconnect_count}次重连请求:{self.tcp_server_host}:{self.tcp_server_port}")
self._connect_to_server()
def _bind_tcp_signals(self):
"""绑定TCP socket的核心信号连接、断开、接收数据、错误"""
# 连接成功信号
self.tcp_socket.connected.connect(self._on_tcp_connected)
# 断开连接信号
self.tcp_socket.disconnected.connect(self._on_tcp_disconnected)
# 接收数据信号(有新数据时触发)
self.tcp_socket.readyRead.connect(self._on_tcp_data_received)
# 错误信号(连接/通信出错时触发)
self.tcp_socket.errorOccurred.connect(self._on_tcp_error)
# ------------------
# TCP客户端核心功能
# ------------------
@Slot()
def _on_tcp_connected(self):
"""TCP连接成功回调"""
self.is_tcp_connected = True
self.has_connected_once = True
self.reconnect_timer.stop() # 停止重连定时器
self.reconnect_count = 0 # 重连计数器清零
self.is_running = True
print(f"TCP连接成功{self.tcp_server_host}:{self.tcp_server_port}")
# 发送连接状态信号
self.connection_status_changed.emit(True)
# 连接成功后,向服务器发送“请求初始数据”指令
self._send_tcp_request("get_initial_data")
@Slot()
def _on_tcp_disconnected(self):
"""TCP连接断开回调"""
self.is_tcp_connected = False
self.is_running = False
print(f"TCP连接断开{self.tcp_server_host}:{self.tcp_server_port}")
# 发送连接状态信号
self.connection_status_changed.emit(False)
# 发送重量清零信号
self.weight_updated.emit({"hopper_up_weight": 0, "hopper_down_weight": 0})
if not self.reconnect_timer.isActive(): # 未启动重连定时器时才启动
print(f"连接断开,将在{RECONNECT_INTERVAL / 1000}秒后启动重连...")
self.reconnect_timer.start()
@Slot()
def _on_tcp_data_received(self):
"""TCP数据接收回调服务器发送数据时触发"""
tcp_data = self.tcp_socket.readAll().data().decode("utf-8").strip()
print(f"TCP数据接收{tcp_data}")
# 解析数据
try:
status_data = json.loads(tcp_data)
self.latest_json_data = status_data
self.weight_updated.emit(status_data)
except json.JSONDecodeError as e:
print(f"TCP数据解析失败非JSON格式{e}, 原始数据:{tcp_data}")
except Exception as e:
print(f"TCP数据处理异常{e}")
@Slot(QAbstractSocket.SocketError)
def _on_tcp_error(self, error):
"""TCP错误回调"""
if not self.is_tcp_connected:
error_str = self.tcp_socket.errorString()
print(f"TCP错误{error_str}")
self.is_tcp_connected = False
self.is_running = False
# 发送连接断开信号
self.connection_status_changed.emit(False)
# 首次连接失败时,启动重连定时器
if not self.reconnect_timer.isActive():
print(f"将在{RECONNECT_INTERVAL / 1000}秒后启动重连")
self.reconnect_timer.start()
def _send_tcp_request(self, request_cmd="get_status"):
"""向TCP服务器发送请求指令"""
if not self.is_tcp_connected:
print("TCP连接未建立无法发送请求")
return
# 构造请求数据
request_data = json.dumps({
"cmd": request_cmd,
"timestamp": self.get_current_time(),
"client_info": "布料系统客户端"
}) + "\n" # 增加换行符作为数据结束标识
# 发送请求数据
self.tcp_socket.write(request_data.encode("utf-8"))
print(f"TCP请求发送{request_data.strip()}")
# ------------------
# 时间相关的通用方法
# ------------------
def get_current_time(self):
"""获取格式化的当前时间"""
return datetime.now().strftime('%Y-%m-%d %H:%M:%S')

228
weight_dialog.py Normal file
View File

@ -0,0 +1,228 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/11/10 11:29
# @Author : reenrr
# @Description : 重量显示界面,显示上料斗重量、下料斗重量
'''
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, QSize, QTimer, Slot
from PySide6.QtNetwork import QTcpSocket, QAbstractSocket
import sys
import json
from datetime import datetime
class WeightDetailsDialog(QDialog):
"""
上料斗、下料斗重量显示界面
"""
def __init__(self, parent=None, title="上料斗重量" ):
super().__init__(parent)
self.setAttribute(Qt.WA_TranslucentBackground)
self.digit_labels = [] #用于存储数字标签的列表
self.default_icon_path = "选择_未实时.png" # 未连接图标
self.connected_icon_path = "选择_实时.png" # 已连接图标
self.check_label = None # 状态图标标签
self.is_running = False # 定时器是否运行
self.latest_json_data = {}
self.statusWidgets = [] # 状态显示控件列表
self._init_ui(title)
# -----------------------
# 界面初始化函数
# -----------------------
def _init_ui(self, title):
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, title)
# 2. 状态显示+重量框
self._add_state_weight_area(main_layout)
def _load_background(self):
self.bg_pixmap = QPixmap("重量背景.png") # 修改为派单任务背景图
if self.bg_pixmap.isNull():
print("错误:重量背景.png 加载失败,请检查路径!")
self.setFixedSize(800, 500)
else:
self.setFixedSize(self.bg_pixmap.size())
def _add_top_area(self, parent_layout, title):
top_layout = QHBoxLayout()
top_layout.setContentsMargins(0, 0, 0, 36)
top_layout.addStretch()
title_label = QLabel(title)
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, alignment=Qt.AlignTop)
# 关闭按钮
top_layout.addStretch()
parent_layout.addLayout(top_layout)
def _add_state_weight_area(self, parent_layout):
grid_layout = QGridLayout()
grid_layout.setSpacing(12)
weight_widget = self._create_info_weight()
grid_layout.addWidget(weight_widget)
parent_layout.addLayout(grid_layout)
def _create_info_weight(self):
# 1、创建最外层水平布局容纳所有元素
weight_layout = QHBoxLayout()
weight_layout.setContentsMargins(0, 0, 0, 0)
weight_layout.setSpacing(0)
# 2、创建确认图标
self.check_label = QLabel()
self._update_status_icon(False)
weight_layout.addWidget(self.check_label)
weight_layout.setSpacing(50)
# 3、创建“6个数字单元格”
digit_font = QFont()
digit_font.setPixelSize(70)
digit_font.setLetterSpacing(QFont.AbsoluteSpacing, 2)
digit_font.setBold(False)
digit_pixmap = QPixmap("数字.png")
digit_size = digit_pixmap.size()
for i in range(6):
# 数字标签
digit_label = QLabel("")
digit_label.setFixedSize(digit_size)
digit_label.setFont(digit_font)
digit_label.setAlignment(Qt.AlignCenter)
digit_label.setStyleSheet("color: white")
# 若图片存在,设置背景图片
if not digit_pixmap.isNull():
digit_label.setStyleSheet("""
QLabel {
color: white;
background-image: url(数字.png);
background-repeat: no-repeat;
background-position: center;
}
""")
weight_layout.addWidget(digit_label)
self.digit_labels.append(digit_label)
if i < 5:
weight_layout.addSpacing(5)
# 4、创建“kg”单位单元格
unit_label = QLabel("kg")
unit_label.setFixedSize(digit_size)
unit_label.setFont(digit_font)
unit_label.setAlignment(Qt.AlignCenter)
if not digit_pixmap.isNull():
unit_label.setStyleSheet("""
QLabel {
color: white;
background-image: url(数字.png);
background-repeat: no-repeat;
background-position: center;
}
""")
weight_layout.addSpacing(5)
weight_layout.addWidget(unit_label)
# 5、将主布局设置到父控件
weight_widget = QWidget()
weight_widget.setLayout(weight_layout)
weight_widget.setObjectName("infoWeight")
return weight_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 paintEvent(self, event):
if not self.bg_pixmap.isNull():
painter = QPainter(self)
painter.drawPixmap(self.rect(), self.bg_pixmap)
super().paintEvent(event)
def update_weight(self, weight_data):
"""根据TCP获取的数据更新重量显示"""
# 更新6个数字标签补零为6位超过6位取后6位
try:
weight_int = int(weight_data)
weight_str = f"{weight_int:06d}"[-6:]
except (ValueError, TypeError):
weight_str = "000000"
for i in range(min(len(self.digit_labels), 6)):
self.digit_labels[i].setText(weight_str[i] if i < len(weight_str) else "0")
def _update_status_icon(self, is_connected):
"""根据连接状态切换图标"""
if not self.check_label:
return
icon_path = self.connected_icon_path if is_connected else self.default_icon_path
pixmap = QPixmap(icon_path)
if not pixmap.isNull():
self.check_label.setPixmap(pixmap.scaled(
pixmap.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))
else:
print(f"错误:{icon_path} 加载失败,请检查路径!")
# --------------------
# 清空界面信息的通用方法
# --------------------
def _clear_ui_info(self):
"""清空管片ID和网格信息"""
if self.id_value_label:
self.id_value_label.setText("")
for widget in self.statusWidgets:
widget['valueLabel'].setText("")
print(" 界面信息已清空")
# 测试代码
if __name__ == "__main__":
app = QApplication(sys.argv)
dialog = WeightDetailsDialog()
dialog.show()
sys.exit(app.exec())

BIN
主界面背景.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
关闭图标.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

BIN
底部背景.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
数字.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

BIN
系统logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
系统总标题.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
系统消息喇叭.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

BIN
系统消息背景.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
选择_实时.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
选择_未实时.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
重量背景.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB