commit ccc794d158407b511de95dfb5c32aadee45fe86d Author: pengqi Date: Sun Nov 16 11:58:22 2025 +0800 V1.0 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..359bb53 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..78dfc6d --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,18 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..62ef35e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..56ad756 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/中交三航背景界面.iml b/.idea/中交三航背景界面.iml new file mode 100644 index 0000000..53e58ed --- /dev/null +++ b/.idea/中交三航背景界面.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/bottom_control_widget.py b/bottom_control_widget.py new file mode 100644 index 0000000..49b3025 --- /dev/null +++ b/bottom_control_widget.py @@ -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()) + \ No newline at end of file diff --git a/config/config.json b/config/config.json new file mode 100644 index 0000000..476559d --- /dev/null +++ b/config/config.json @@ -0,0 +1,4 @@ +{ + "hopper_up_weight": 1111, + "hopper_down_weight": 6524 +} diff --git a/config/tcp_server.py b/config/tcp_server.py new file mode 100644 index 0000000..5789f02 --- /dev/null +++ b/config/tcp_server.py @@ -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() diff --git a/main_window.py b/main_window.py new file mode 100644 index 0000000..eae48b4 --- /dev/null +++ b/main_window.py @@ -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()) diff --git a/system_nav_bar.py b/system_nav_bar.py new file mode 100644 index 0000000..35ed612 --- /dev/null +++ b/system_nav_bar.py @@ -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()) \ No newline at end of file diff --git a/tcp_client.py b/tcp_client.py new file mode 100644 index 0000000..1a534c5 --- /dev/null +++ b/tcp_client.py @@ -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') \ No newline at end of file diff --git a/weight_dialog.py b/weight_dialog.py new file mode 100644 index 0000000..80f4ee3 --- /dev/null +++ b/weight_dialog.py @@ -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()) diff --git a/主界面背景.png b/主界面背景.png new file mode 100644 index 0000000..dc65b12 Binary files /dev/null and b/主界面背景.png differ diff --git a/关闭图标.png b/关闭图标.png new file mode 100644 index 0000000..9b090d6 Binary files /dev/null and b/关闭图标.png differ diff --git a/底部背景.png b/底部背景.png new file mode 100644 index 0000000..3a66c09 Binary files /dev/null and b/底部背景.png differ diff --git a/数字.png b/数字.png new file mode 100644 index 0000000..2c44b01 Binary files /dev/null and b/数字.png differ diff --git a/系统logo.png b/系统logo.png new file mode 100644 index 0000000..e50db73 Binary files /dev/null and b/系统logo.png differ diff --git a/系统主界面导航栏.png b/系统主界面导航栏.png new file mode 100644 index 0000000..7db6d8b Binary files /dev/null and b/系统主界面导航栏.png differ diff --git a/系统总标题.png b/系统总标题.png new file mode 100644 index 0000000..64f34be Binary files /dev/null and b/系统总标题.png differ diff --git a/系统消息喇叭.png b/系统消息喇叭.png new file mode 100644 index 0000000..abbc7f2 Binary files /dev/null and b/系统消息喇叭.png differ diff --git a/系统消息背景.png b/系统消息背景.png new file mode 100644 index 0000000..a436582 Binary files /dev/null and b/系统消息背景.png differ diff --git a/选择_实时.png b/选择_实时.png new file mode 100644 index 0000000..0599656 Binary files /dev/null and b/选择_实时.png differ diff --git a/选择_未实时.png b/选择_未实时.png new file mode 100644 index 0000000..a168e17 Binary files /dev/null and b/选择_未实时.png differ diff --git a/重量背景.png b/重量背景.png new file mode 100644 index 0000000..41d0797 Binary files /dev/null and b/重量背景.png differ