diff --git a/zjsh_ui_sysytem b/zjsh_ui_sysytem
deleted file mode 160000
index fe751e8..0000000
--- a/zjsh_ui_sysytem
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit fe751e8dd209b7a4bb8ac88b27a7291d347e20b9
diff --git a/zjsh_ui_sysytem/.idea/.gitignore b/zjsh_ui_sysytem/.idea/.gitignore
new file mode 100644
index 0000000..359bb53
--- /dev/null
+++ b/zjsh_ui_sysytem/.idea/.gitignore
@@ -0,0 +1,3 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
diff --git a/zjsh_ui_sysytem/.idea/inspectionProfiles/Project_Default.xml b/zjsh_ui_sysytem/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..78dfc6d
--- /dev/null
+++ b/zjsh_ui_sysytem/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/.idea/inspectionProfiles/profiles_settings.xml b/zjsh_ui_sysytem/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/zjsh_ui_sysytem/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/.idea/misc.xml b/zjsh_ui_sysytem/.idea/misc.xml
new file mode 100644
index 0000000..62ef35e
--- /dev/null
+++ b/zjsh_ui_sysytem/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/.idea/modules.xml b/zjsh_ui_sysytem/.idea/modules.xml
new file mode 100644
index 0000000..acf6533
--- /dev/null
+++ b/zjsh_ui_sysytem/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/.idea/vcs.xml b/zjsh_ui_sysytem/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/zjsh_ui_sysytem/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/.idea/zjsh_ui_sysytem.iml b/zjsh_ui_sysytem/.idea/zjsh_ui_sysytem.iml
new file mode 100644
index 0000000..53e58ed
--- /dev/null
+++ b/zjsh_ui_sysytem/.idea/zjsh_ui_sysytem.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/README.md b/zjsh_ui_sysytem/README.md
new file mode 100644
index 0000000..edfe241
--- /dev/null
+++ b/zjsh_ui_sysytem/README.md
@@ -0,0 +1,3 @@
+# zjsh_ui_sysytem
+
+中交三航精准布料浇筑要料系统
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/config/config.json b/zjsh_ui_sysytem/config/config.json
new file mode 100644
index 0000000..65a36b5
--- /dev/null
+++ b/zjsh_ui_sysytem/config/config.json
@@ -0,0 +1,12 @@
+{
+ "task_id": "20250706-01",
+ "project_name": "18号线二期工程",
+ "section": "停车场工作井上行",
+ "slump": "50~70 mm",
+ "mix_ratio_id": "P2022=001",
+ "request_status": "请求中",
+ "material_grade": "C50P12",
+ "volume": "2m³",
+ "request_time": "10分钟后",
+ "car_status": "移动后"
+}
diff --git a/zjsh_ui_sysytem/config/tcp_server.py b/zjsh_ui_sysytem/config/tcp_server.py
new file mode 100644
index 0000000..b46d34a
--- /dev/null
+++ b/zjsh_ui_sysytem/config/tcp_server.py
@@ -0,0 +1,191 @@
+#!/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 = {
+ "task_id": "None",
+ "project_name": "None",
+ "section": "None",
+ "slump": "None",
+ "mix_ratio_id": "None",
+ "request_status": "None",
+ "material_grade": "None",
+ "volume": "None",
+ "request_time": "None",
+ "car_status": "None"
+ }
+
+ 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} 不存在,将使用默认数据")
+ # 创建默认配置文件
+ self.create_default_config()
+ except Exception as e:
+ print(f"加载配置文件时发生错误:{e},将使用默认数据")
+ self.data_template = None
+
+ def create_default_config(self):
+ """创建默认配置文件"""
+ default_data = {
+ "task_id": "None",
+ "project_name": "None",
+ "section": "None",
+ "slump": "None",
+ "mix_ratio_id": "None",
+ "request_status": "None",
+ "material_grade": "None",
+ "volume": "None",
+ "request_time": "None",
+ "car_status": "None"
+ }
+
+ try:
+ with open(self.config_file, 'w', encoding='utf-8') as f:
+ json.dump(default_data, f, ensure_ascii=False, indent=4)
+ print(f"已创建默认配置文件 {self.config_file}")
+ self.data_template = default_data
+ 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/zjsh_ui_sysytem/img.png b/zjsh_ui_sysytem/img.png
new file mode 100644
index 0000000..975dfcd
Binary files /dev/null and b/zjsh_ui_sysytem/img.png differ
diff --git a/zjsh_ui_sysytem/img_1.png b/zjsh_ui_sysytem/img_1.png
new file mode 100644
index 0000000..21a7abc
Binary files /dev/null and b/zjsh_ui_sysytem/img_1.png differ
diff --git a/zjsh_ui_sysytem/img_2.png b/zjsh_ui_sysytem/img_2.png
new file mode 100644
index 0000000..2799737
Binary files /dev/null and b/zjsh_ui_sysytem/img_2.png differ
diff --git a/zjsh_ui_sysytem/main1.py b/zjsh_ui_sysytem/main1.py
new file mode 100644
index 0000000..f844033
--- /dev/null
+++ b/zjsh_ui_sysytem/main1.py
@@ -0,0 +1,587 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+'''
+# @Time : 2025/9/10 11:29
+# @Author : reenrr
+# @File : test.py
+'''
+import sys
+from PySide6.QtWidgets import (
+ QApplication, QWidget, QVBoxLayout, QHBoxLayout,
+ QLabel, QFrame, QSizePolicy, QPushButton
+)
+from PySide6.QtCore import Qt, QTimer, QPoint, QSize, Slot
+from PySide6.QtGui import QFont, QPainter, QColor, QPen, QIcon
+from datetime import datetime
+from PySide6.QtNetwork import QTcpSocket, QAbstractSocket
+import json
+import os
+
+
+# -----------
+# 参数配置
+# -----------
+tcp_server_host = "127.0.0.1"
+tcp_server_port = 8888
+
+# 数据保存目录
+SAVE_DIR = "operation_records"
+
+class StatusMonitor(QWidget):
+ """
+ 中交三航精准布料浇筑要料系统 - 主界面类(深色主题)
+ 使用TCP进行数据传输(客户端模型,与TCP服务器通信)
+ """
+
+ def __init__(self, parent=None):
+ """构造函数:初始化主界面的UI布局、控件和定时器"""
+ super().__init__(parent=parent)
+ self.is_running = False # 系统运行状态标记
+ self.current_datetime = self.get_current_time() # 当前日期时间
+ # 缓存服务端发送的最新JSON数据
+ self.latest_json_data = {}
+
+ # ---------------
+ # 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连接状态标记
+
+ # 绑定TCP信号与槽(事件驱动)
+ self._bind_tcp_signals()
+
+ # 窗口基础设置
+ self.setWindowTitle("中交三航精准布料浇筑要料系统")
+ self.setGeometry(100, 100, 850, 500) # 设置窗口位置和大小
+ self.setStyleSheet("background-color: #121212;") # 窗口背景设为深黑色
+
+ # 初始化主布局(垂直布局)
+ self.mainLayout = QVBoxLayout(self)
+ self.mainLayout.setContentsMargins(10, 40, 10, 10) # 上边距留空用于显示日期
+ self.mainLayout.setSpacing(10) # 相邻控件的间距
+
+ # 初始化界面组件
+ self._init_title_label()
+ self._init_status_container()
+ self._init_timers()
+ self._init_save_dir()
+
+ # ----------------
+ # 客户端自动后自动连接服务端
+ # ----------------
+ print(f"客户端启动,自动连接服务端{self.tcp_server_host}:{self.tcp_server_port}...")
+ self.tcp_socket.connectToHost(self.tcp_server_host, self.tcp_server_port)
+
+ def _init_save_dir(self):
+ """初始化数据保存目录"""
+ if not os.path.exists(SAVE_DIR):
+ os.makedirs(SAVE_DIR)
+ print(f"已创建数据保存目录:{os.path.abspath(SAVE_DIR)}")
+ else:
+ print(f"数据保存目录已存在:{os.path.abspath(SAVE_DIR)}")
+
+ 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)
+
+ def _init_title_label(self):
+ """初始化系统标题标签"""
+ titleLabel = QLabel("中交三航精准布料浇筑要料系统")
+ titleLabel.setStyleSheet("""
+ QLabel {
+ color: #00FF9D;
+ font-size: 20px;
+ font-weight: bold;
+ }
+ """)
+ titleLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ titleLabel.setFont(QFont("Microsoft YaHei", 24, QFont.Bold))
+ self.mainLayout.addWidget(titleLabel)
+
+ def _init_status_container(self):
+ """初始化核心状态监控容器(包含状态组和操作按钮)"""
+ self.bigContainer = QFrame()
+ self.bigContainer.setStyleSheet("""
+ QFrame {
+ background-color: #1E1E1E;
+ border: 2px solid #333333;
+ border-radius: 8px;
+ }
+ """)
+ self.bigContainer.setSizePolicy(
+ QSizePolicy.Policy.Expanding,
+ QSizePolicy.Policy.Expanding
+ )
+
+ containerLayout = QVBoxLayout(self.bigContainer)
+ containerLayout.setContentsMargins(20, 20, 20, 20)
+
+ self._init_status_groups(containerLayout)
+ self._init_operation_buttons(containerLayout)
+
+ self.mainLayout.addWidget(self.bigContainer, 1)
+
+ def _init_status_groups(self, parent_layout):
+ """初始化左右信息(各包含5个状态项)"""
+ statusWidget = QWidget()
+ statusLayout = QHBoxLayout(statusWidget)
+ statusLayout.setContentsMargins(0, 0, 0, 0)
+ statusLayout.setSpacing(30) # 减小中间空白间距(原30)
+
+ leftGroup = QWidget()
+ leftLayout = QVBoxLayout(leftGroup)
+ leftLayout.setSpacing(15)
+ leftLayout.setContentsMargins(30, 0, 0, 0) # 左边组左内边距设为20,增加左边留白
+
+ rightGroup = QWidget()
+ rightLayout = QVBoxLayout(rightGroup)
+ rightLayout.setSpacing(15)
+ rightLayout.setContentsMargins(0, 0, 30, 0) # 右边组右内边距设为20,增加右边留白(若需左边也留白,可设左内边距)
+
+ # 左边5个状态项及对应初始值
+ leftStatusInfo = [
+ {"name": "任务单号", "value": "20250706-01", "api_field": "task_id"},
+ {"name": "工程名称", "value": "18号线二期工程", "api_field": "project_name"},
+ {"name": "区间段", "value": "停车场工作并上行", "api_field": "section"},
+ {"name": "坍落度", "value": "50~70 mm", "api_field": "slump"},
+ {"name": "配合比编号", "value": "P2022=001", "api_field": "mix_ratio_id"}
+ ]
+ # 右边5个状态项及对应初始值
+ rightStatusInfo = [
+ {"name": "要料状态", "value": "请求中", "api_field": "request_status"},
+ {"name": "要料标号", "value": "C50P12", "api_field": "material_grade"},
+ {"name": "要料方量", "value": "2m³", "api_field": "volume"},
+ {"name": "要料时间", "value": "2分钟后", "api_field": "request_time"},
+ {"name": "小车状态", "value": "移动后", "api_field": "car_status"}
+ ]
+ self.statusWidgets = []
+
+ # 处理左边状态项
+ for info in leftStatusInfo:
+ statusItem = self._create_status_item(info)
+ leftLayout.addWidget(statusItem)
+
+ # 处理右边状态项
+ for info in rightStatusInfo:
+ statusItem = self._create_status_item(info)
+ rightLayout.addWidget(statusItem)
+
+ statusLayout.addWidget(leftGroup)
+ statusLayout.addStretch(0) # 减小中间空白比例(原1)
+ statusLayout.addWidget(rightGroup)
+
+ parent_layout.addWidget(statusWidget)
+
+ def _create_status_item(self, info):
+ """创建单个状态项"""
+ statusItem = QFrame()
+ statusItem.setStyleSheet("""
+ QFrame {
+ background-color: #2D2D2D;
+ border: 1px solid #444444;
+ border-radius: 6px;
+ padding: 10px;
+ }
+ """)
+ statusItem.setFixedHeight(80)
+ statusItem.setFixedWidth(320) # 统一加长状态项宽度(原无固定宽度)
+
+ itemLayout = QHBoxLayout(statusItem)
+ itemLayout.setContentsMargins(10, 5, 10, 5)
+
+ # 状态指示灯
+ indicator = QLabel()
+ indicator.setFixedSize(20, 20)
+ indicator.setStyleSheet("""
+ QLabel {
+ background-color: #9E9E9E;
+ border-radius: 10px;
+ border: 2px solid #555555;
+ }
+ """)
+
+ # 状态名称标签
+ nameLabel = QLabel(info["name"])
+ nameLabel.setFixedWidth(100) # 加宽名称标签(原90)
+ nameLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ nameLabel.setStyleSheet("font-size: 14px; color: #FFFFFF;")
+ nameLabel.setFont(QFont("Microsoft YaHei", 12))
+
+ # 状态值标签
+ valueLabel = QLabel(info["value"])
+ valueLabel.setStyleSheet("""
+ QLabel {
+ font-size: 16px;
+ font-weight: bold;
+ color: #FFFFFF;
+ background-color: #2D2D2D;
+ border: none;
+ padding: 0px;
+ }
+ """)
+ valueLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ valueLabel.setMinimumWidth(150) # 加宽值标签(原80)
+
+ itemLayout.addWidget(indicator)
+ itemLayout.addSpacing(10)
+ itemLayout.addWidget(nameLabel)
+ itemLayout.addStretch()
+ itemLayout.addWidget(valueLabel)
+
+ self.statusWidgets.append({
+ 'indicator': indicator,
+ 'nameLabel': nameLabel,
+ 'valueLabel': valueLabel,
+ 'status': False,
+ 'initial_value': info["value"],
+ 'api_field': info["api_field"]
+ })
+
+ return statusItem
+
+ def _init_operation_buttons(self, parent_layout):
+ """初始化操作按钮(下料完成/生产异常/生产取消)"""
+ buttonContainer = QWidget()
+ buttonLayout = QHBoxLayout(buttonContainer)
+ buttonLayout.setContentsMargins(0, 20, 0, 0)
+ buttonLayout.setSpacing(30)
+
+ # 按钮图标(需替换为实际图标路径)
+ start_icon_path = "img.png"
+ down_icon_path = "img.png"
+ error_icon_path = "img.png"
+ cancel_icon_path = "img.png"
+
+ self.startFeedButton = QPushButton("开始下料")
+ self.finishButton = QPushButton("下料完成")
+ self.errorButton = QPushButton("生产异常")
+ self.cancelButton = QPushButton("生产取消")
+
+ # 设置按钮图标
+ self.startFeedButton.setIcon(QIcon(start_icon_path))
+ self.finishButton.setIcon(QIcon(down_icon_path))
+ self.errorButton.setIcon(QIcon(error_icon_path))
+ self.cancelButton.setIcon(QIcon(cancel_icon_path))
+
+ # 设置图标大小
+ self.startFeedButton.setIconSize(QSize(20, 20))
+ self.finishButton.setIconSize(QSize(20, 20))
+ self.errorButton.setIconSize(QSize(20, 20))
+ self.cancelButton.setIconSize(QSize(20, 20))
+
+ button_base_style = """
+ QPushButton {
+ font-size: 16px;
+ font-weight: bold;
+ padding: 10px 20px;
+ border-radius: 15px;
+ border: 1px solid;
+ min-width: 100px;
+ }
+ QPushButton:hover {
+ opacity: 0.9;
+ }
+ QPushButton:pressed {
+ opacity: 0.8;
+ }
+ """
+
+ self.startFeedButton.setStyleSheet(button_base_style + """
+ QPushButton {
+ background-color: #2196F3;
+ color: white;
+ border-color: #1976D2;
+ }
+ """)
+
+ self.finishButton.setStyleSheet(button_base_style + """
+ QPushButton {
+ background-color: #00796B;
+ color: white;
+ border-color: #004D40;
+ }
+ """)
+
+ self.errorButton.setStyleSheet(button_base_style + """
+ QPushButton {
+ background-color: #E65100;
+ color: white;
+ border-color: #BF360C;
+ }
+ """)
+
+ self.cancelButton.setStyleSheet(button_base_style + """
+ QPushButton {
+ background-color: #C62828;
+ color: white;
+ border-color: #8E0000;
+ }
+ """)
+
+ button_font = QFont("Microsoft YaHei", 12, QFont.Bold)
+ self.startFeedButton.setFont(button_font)
+ self.finishButton.setFont(button_font)
+ self.errorButton.setFont(button_font)
+ self.cancelButton.setFont(button_font)
+
+ self.startFeedButton.clicked.connect(self.on_start_clicked)
+ self.finishButton.clicked.connect(self.on_finish_clicked)
+ self.errorButton.clicked.connect(self.on_error_clicked)
+ self.cancelButton.clicked.connect(self.on_cancel_clicked)
+
+ # 初始禁用“停止”和“异常”按钮(未连接时不可用)
+ self.startFeedButton.setDisabled(False)
+ self.finishButton.setDisabled(True)
+ self.errorButton.setDisabled(True)
+ self.cancelButton.setDisabled(True)
+
+ buttonLayout.addStretch(1)
+ buttonLayout.addWidget(self.startFeedButton)
+ buttonLayout.addWidget(self.finishButton)
+ buttonLayout.addWidget(self.errorButton)
+ buttonLayout.addWidget(self.cancelButton)
+ buttonLayout.addStretch(1)
+
+ parent_layout.addWidget(buttonContainer)
+
+ def _init_timers(self):
+ """初始化定时器(时间更新+状态模拟)"""
+ # 时间更新定时器(每秒更新一次)
+ self.time_timer = QTimer()
+ self.time_timer.timeout.connect(self.update_time)
+ self.time_timer.start(1000)
+
+ def get_current_time(self):
+ """获取格式化的当前时间"""
+ return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+
+ def get_timestamp(self):
+ """获取当前时间戳(秒级)"""
+ return datetime.now().strftime('%Y%m%d_%H%M%S_%f')[:-3]
+
+ def update_time(self):
+ """更新时间显示并触发重绘"""
+ self.current_datetime = self.get_current_time()
+ self.update() # 触发paintEvent重绘
+
+ def paintEvent(self, event):
+ """重写绘画事件,在右上角绘制日期时间文本"""
+ super().paintEvent(event) # 调用父类方法保持原有绘制
+
+ # 创建QPainter对象
+ painter = QPainter(self)
+ painter.setRenderHint(QPainter.RenderHint.TextAntialiasing) # 文本抗锯齿
+
+ # 设置字体
+ font = QFont("Arial", 12, QFont.Bold)
+ painter.setFont(font)
+
+ # 设置文本颜色
+ painter.setPen(QColor("#00FF9D"))
+
+ # 计算文本位置(右上角,留出边距)
+ text = f"🕒 {self.current_datetime}"
+ text_rect = painter.boundingRect(self.rect(), Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop, text)
+ x = self.width() - text_rect.width() - 15 # 右边距15px
+ y = 15 # 上边距15px
+
+ # 绘制文本
+ painter.drawText(x, y + text_rect.height(), text)
+
+ def _save_data_to_file(self, button_name):
+ """
+ 将服务端数据+按钮操作信息保存到JSON文件
+
+ 参数:button_name:点击的按钮名称
+ """
+ # 1、检查是否有服务端数据
+ if not self.latest_server_data:
+ print(f"⚠️ 未收到服务端数据,无法保存「{button_name}」操作记录")
+ return
+
+ # 2、构建完整数据(服务端数据+按钮操作信息+存档时间)
+ save_data = {
+ "opration_button": button_name,
+ "save_time": self.get_current_time(),
+ "server_data":self.latest_server_data
+ }
+
+ # 3、生成唯一文件名(按时间戳命名)
+ file_name = f"operation_record_{self.get_timestamp()}.json"
+ file_path = os.path.join(SAVE_DIR, file_name)
+
+ # 4、写入JSON文件
+ try:
+ with open(file_path, "w", encoding="utf-8") as f:
+ json.dump(save_data, f, ensure_ascii=False, indent=4)
+ print(f"💾 保存「{button_name}」操作记录成功:{os.path.abspath(file_path)}")
+ except Exception as e:
+ print(f"💾 保存「{button_name}」操作记录失败:{str(e)}")
+
+ # ------------------
+ # TCP客户端核心功能
+ # ------------------
+ @Slot()
+ def _on_tcp_connected(self):
+ """TCP连接成功回调"""
+ self.is_tcp_connected = True
+ self.is_running = True
+ print(f"TCP连接成功:{self.tcp_server_host}:{self.tcp_server_port}")
+
+ # 连接成功后,向服务器发送“请求初始数据”指令
+ self._send_tcp_request("get_initial_data")
+
+ # 更新按钮状态:启用“下料完成”“生产异常”“生产取消”
+ self.finishButton.setDisabled(False)
+ self.errorButton.setDisabled(False)
+ self.cancelButton.setDisabled(False)
+
+ @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.startFeedButton.setDisabled(False)
+ self.finishButton.setDisabled(True)
+ self.errorButton.setDisabled(True)
+ self.cancelButton.setDisabled(True)
+
+ # 重置状态指示灯为“未连接”状态
+ for widget in self.statusWidgets:
+ widget['indicator'].setStyleSheet("""
+ QLabel {
+ background-color: #9E9E9E;
+ border-radius: 10px;
+ border: 2px solid #555555;
+ }
+ """)
+
+ @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_server_data = status_data
+ self._update_ui_from_data(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错误回调"""
+ error_str = self.tcp_socket.errorString()
+ print(f"TCP错误:{error_str}")
+ self.is_tcp_connected = False
+ self.is_running = False
+
+ # 启用/禁用按钮
+ self.startFeedButton.setDisabled(False)
+ self.finishButton.setDisabled(True)
+ self.errorButton.setDisabled(True)
+ self.cancelButton.setDisabled(True)
+
+ 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 _update_ui_from_data(self, data):
+ """根据TCP获取的数据更新界面状态"""
+ for widget in self.statusWidgets:
+ api_field = widget['api_field']
+ if api_field in data:
+ new_value = str(data[api_field])
+ widget['valueLabel'].setText(new_value)
+ self.set_indicator_status(widget, new_value)
+
+ # ------------------
+ # 状态指示灯逻辑
+ # ------------------
+ def set_indicator_status(self, widget, value):
+ """根据值设置状态指示灯颜色"""
+ if value and value != "未知" and value != "" and value != "None":
+ # 有效数据:绿色指示灯
+ widget['indicator'].setStyleSheet("""
+ QLabel {
+ background-color: #00E676;
+ border-radius: 10px;
+ border: 2px solid #00796B;
+ }
+ """)
+ else:
+ # 无效数据:红色指示灯
+ widget['indicator'].setStyleSheet("""
+ QLabel {
+ background-color: #FF5252;
+ border-radius: 10px;
+ border: 2px solid #C62828;
+ }
+ """)
+
+ # ------------------
+ # 按钮点击事件
+ # ------------------
+ def on_start_clicked(self):
+ """点击“开始下料”:向服务端发送开始指令"""
+ print("🔘 点击「开始下料」按钮")
+ if not self.is_tcp_connected:
+ print("TCP连接未建立,尝试重新连接")
+ self.tcp_socket.connectToHost(self.tcp_server_host, self.tcp_server_port)
+ QTimer.singleShot(1000, lambda: self._send_tcp_request("start_feed")) # 等待连接成功后再发送请求
+ else:
+ print("TCP连接已建立")
+ self._send_tcp_request("start_feed")
+
+ def on_finish_clicked(self):
+ """点击“下料完成”:向服务端发送完成指令"""
+ print("🔘 点击「下料完成」按钮")
+ self._send_tcp_request("finish_feed")
+ self._save_data_to_file("finish_feed")
+
+ def on_error_clicked(self):
+ """点击“生产异常”:向服务端发送异常指令"""
+ print("🔘 点击「生产异常」按钮")
+ self._send_tcp_request("production_error")
+ self._save_data_to_file("production_error")
+
+ def on_cancel_clicked(self):
+ """点击“生产取消”:向服务端发送取消指令"""
+ print("🔘 点击「生产取消」按钮")
+ self._send_tcp_request("cancel_feed")
+ self._save_data_to_file("cancel_feed")
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ window = StatusMonitor()
+ window.show()
+ sys.exit(app.exec())
diff --git a/zjsh_ui_sysytem/main2.py b/zjsh_ui_sysytem/main2.py
new file mode 100644
index 0000000..f6a5916
--- /dev/null
+++ b/zjsh_ui_sysytem/main2.py
@@ -0,0 +1,569 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+'''
+# @Time : 2025/10/31 14:39
+# @Author : reenrr
+# @Description : 通过tcp连接获取信息,并显示在界面上(版本2)
+# @File : main2.py
+'''
+import sys
+from PySide6.QtWidgets import (
+ QApplication, QWidget, QVBoxLayout, QHBoxLayout,
+ QLabel, QFrame, QSizePolicy, QPushButton
+)
+from PySide6.QtCore import Qt, QTimer, QPoint, QSize, Slot
+from PySide6.QtGui import QFont, QPainter, QColor, QPen, QIcon
+from datetime import datetime
+from PySide6.QtNetwork import QTcpSocket, QAbstractSocket
+import json
+import os
+
+
+# -----------
+# 参数配置
+# -----------
+tcp_server_host = "127.0.0.1"
+tcp_server_port = 8888
+
+# 数据保存目录
+SAVE_DIR = "operation_records"
+
+class StatusMonitor(QWidget):
+ """
+ 中交三航精准布料浇筑要料系统 - 主界面类(深色主题)
+ 使用TCP进行数据传输(客户端模型,与TCP服务器通信)
+ """
+
+ def __init__(self, parent=None):
+ """构造函数:初始化主界面的UI布局、控件和定时器"""
+ super().__init__(parent=parent)
+ self.is_running = False # 系统运行状态标记
+ self.current_datetime = self.get_current_time() # 当前日期时间
+ # 缓存服务端发送的最新JSON数据(统一变量名,避免保存时出错)
+ self.latest_server_data = {}
+
+ # ---------------
+ # 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连接状态标记
+
+ # 绑定TCP信号与槽(事件驱动)
+ self._bind_tcp_signals()
+
+ # 窗口基础设置
+ self.setWindowTitle("中交三航精准布料浇筑要料系统")
+ self.setGeometry(100, 100, 850, 500) # 设置窗口位置和大小
+ self.setStyleSheet("background-color: #121212;") # 窗口背景设为深黑色
+
+ # 初始化主布局(垂直布局)
+ self.mainLayout = QVBoxLayout(self)
+ self.mainLayout.setContentsMargins(10, 40, 10, 10) # 上边距留空用于显示日期
+ self.mainLayout.setSpacing(10) # 相邻控件的间距
+
+ # 初始化界面组件
+ self._init_title_label()
+ self._init_status_container()
+ self._init_timers()
+
+ # ----------------
+ # 客户端自动连接服务端
+ # ----------------
+ print(f"客户端启动,自动连接服务端{self.tcp_server_host}:{self.tcp_server_port}...")
+ self.tcp_socket.connectToHost(self.tcp_server_host, self.tcp_server_port)
+
+ 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)
+
+ # ----------------
+ # 界面初始化函数
+ # ----------------
+ def _init_title_label(self):
+ """初始化系统标题标签"""
+ titleLabel = QLabel("中交三航精准布料浇筑要料系统")
+ titleLabel.setStyleSheet("""
+ QLabel {
+ color: #00FF9D;
+ font-size: 20px;
+ font-weight: bold;
+ }
+ """)
+ titleLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ titleLabel.setFont(QFont("Microsoft YaHei", 24, QFont.Bold))
+ self.mainLayout.addWidget(titleLabel)
+
+ def _init_status_container(self):
+ """初始化核心状态监控容器(包含状态组和操作按钮)"""
+ self.bigContainer = QFrame()
+ self.bigContainer.setStyleSheet("""
+ QFrame {
+ background-color: #1E1E1E;
+ border: 2px solid #333333;
+ border-radius: 8px;
+ }
+ """)
+ self.bigContainer.setSizePolicy(
+ QSizePolicy.Policy.Expanding,
+ QSizePolicy.Policy.Expanding
+ )
+
+ containerLayout = QVBoxLayout(self.bigContainer)
+ containerLayout.setContentsMargins(20, 20, 20, 20)
+
+ self._init_status_groups(containerLayout)
+ self._init_operation_buttons(containerLayout) # 操作按钮包含新增的3个状态按钮
+
+ self.mainLayout.addWidget(self.bigContainer, 1)
+
+ def _init_status_groups(self, parent_layout):
+ """初始化左右信息(各包含3个状态项)"""
+ statusWidget = QWidget()
+ statusLayout = QHBoxLayout(statusWidget)
+ statusLayout.setContentsMargins(0, 0, 0, 0)
+ statusLayout.setSpacing(30) # 减小中间空白间距
+
+ leftGroup = QWidget()
+ leftLayout = QVBoxLayout(leftGroup)
+ leftLayout.setSpacing(15)
+ leftLayout.setContentsMargins(30, 0, 0, 0) # 左边组左内边距
+
+ rightGroup = QWidget()
+ rightLayout = QVBoxLayout(rightGroup)
+ rightLayout.setSpacing(15)
+ rightLayout.setContentsMargins(0, 0, 30, 0) # 右边组右内边距
+
+ # 左边状态项
+ leftStatusInfo = [
+ {"name": "任务单号", "value": "", "api_field": "task_id"},
+ {"name": "工程名称", "value": "", "api_field": "project_name"},
+ {"name": "配比号", "value": "", "api_field": "produce_mix_id"}
+ ]
+ # 右边状态项
+ rightStatusInfo = [
+ {"name": "要料状态", "value": "", "api_field": "flag"},
+ {"name": "砼强度", "value": "", "api_field": "beton_grade"},
+ {"name": "要料方量", "value": "", "api_field": "adjusted_volume"},
+ ]
+ self.statusWidgets = []
+
+ # 处理左边状态项
+ for info in leftStatusInfo:
+ statusItem = self._create_status_item(info)
+ leftLayout.addWidget(statusItem)
+
+ # 处理右边状态项
+ for info in rightStatusInfo:
+ statusItem = self._create_status_item(info)
+ rightLayout.addWidget(statusItem)
+
+ statusLayout.addWidget(leftGroup)
+ statusLayout.addStretch(0)
+ statusLayout.addWidget(rightGroup)
+
+ parent_layout.addWidget(statusWidget)
+
+ def _create_status_item(self, info):
+ """创建单个状态项"""
+ statusItem = QFrame()
+ statusItem.setStyleSheet("""
+ QFrame {
+ background-color: #2D2D2D;
+ border: 1px solid #444444;
+ border-radius: 6px;
+ padding: 10px;
+ }
+ """)
+ statusItem.setFixedHeight(80)
+ statusItem.setFixedWidth(320) # 统一状态项宽度
+
+ itemLayout = QHBoxLayout(statusItem)
+ itemLayout.setContentsMargins(10, 5, 10, 5)
+
+ # 状态指示灯
+ indicator = QLabel()
+ indicator.setFixedSize(20, 20)
+ indicator.setStyleSheet("""
+ QLabel {
+ background-color: #9E9E9E;
+ border-radius: 10px;
+ border: 2px solid #555555;
+ }
+ """)
+
+ # 状态名称标签
+ nameLabel = QLabel(info["name"])
+ nameLabel.setFixedWidth(100)
+ nameLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ nameLabel.setStyleSheet("font-size: 14px; color: #FFFFFF;")
+ nameLabel.setFont(QFont("Microsoft YaHei", 12))
+
+ # 状态值标签
+ valueLabel = QLabel(info["value"])
+ valueLabel.setStyleSheet("""
+ QLabel {
+ font-size: 16px;
+ font-weight: bold;
+ color: #FFFFFF;
+ background-color: #2D2D2D;
+ border: none;
+ padding: 0px;
+ }
+ """)
+ valueLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ valueLabel.setMinimumWidth(150)
+
+ itemLayout.addWidget(indicator)
+ itemLayout.addSpacing(10)
+ itemLayout.addWidget(nameLabel)
+ itemLayout.addStretch()
+ itemLayout.addWidget(valueLabel)
+
+ self.statusWidgets.append({
+ 'indicator': indicator,
+ 'nameLabel': nameLabel,
+ 'valueLabel': valueLabel,
+ 'status': False,
+ 'initial_value': info["value"],
+ 'api_field': info["api_field"]
+ })
+
+ return statusItem
+
+ def _init_operation_buttons(self, parent_layout):
+ """初始化操作按钮(新增未下料/下料中/下料完成 + 原有生产异常/生产取消)"""
+ buttonContainer = QWidget()
+ buttonLayout = QHBoxLayout(buttonContainer)
+ buttonLayout.setContentsMargins(0, 20, 0, 0)
+ buttonLayout.setSpacing(20) # 按钮间距(适配5个按钮,避免拥挤)
+
+ # 按钮图标(复用现有图标路径,可根据需求替换)
+ status_icon_path = "img.png" # 状态类按钮(未下料/下料中/下料完成)共用图标
+ error_icon_path = "img.png"
+ cancel_icon_path = "img.png"
+
+ self.notFeedingBtn = QPushButton("未下料") # 未下料按钮
+ self.feedingBtn = QPushButton("下料中") # 下料中按钮
+ self.feedFinishBtn = QPushButton("下料完成")# 下料完成按钮
+ self.errorButton = QPushButton("生产异常")
+ self.cancelButton = QPushButton("生产取消")
+
+ # 设置按钮设置(图标、大小、样式)
+ status_buttons = [self.notFeedingBtn, self.feedingBtn, self.feedFinishBtn]
+ for btn in status_buttons:
+ btn.setIcon(QIcon(status_icon_path))
+ btn.setIconSize(QSize(20, 20))
+ btn.setFont(QFont("Microsoft YaHei", 12, QFont.Bold))
+ self.errorButton.setIcon(QIcon(error_icon_path))
+ self.cancelButton.setIcon(QIcon(cancel_icon_path))
+ self.errorButton.setIconSize(QSize(20, 20))
+ self.cancelButton.setIconSize(QSize(20, 20))
+ self.errorButton.setFont(QFont("Microsoft YaHei", 12, QFont.Bold))
+ self.cancelButton.setFont(QFont("Microsoft YaHei", 12, QFont.Bold))
+
+ # 按钮基础样式(统一风格)
+ button_base_style = """
+ QPushButton {
+ font-size: 16px;
+ font-weight: bold;
+ padding: 10px 15px; /* 缩小内边距,适配5个按钮布局 */
+ border-radius: 15px;
+ border: 1px solid;
+ min-width: 90px; /* 缩小最小宽度 */
+ }
+ QPushButton:hover {
+ opacity: 0.9;
+ }
+ QPushButton:pressed {
+ opacity: 0.8;
+ }
+ """
+
+ # 单个按钮样式(不同颜色区分状态)
+ self.notFeedingBtn.setStyleSheet(button_base_style + """
+ QPushButton {
+ background-color: #616161; /* 灰色:未开始状态 */
+ color: white;
+ border-color: #424242;
+ }
+ """)
+ self.feedingBtn.setStyleSheet(button_base_style + """
+ QPushButton {
+ background-color: #2196F3; /* 蓝色:进行中状态 */
+ color: white;
+ border-color: #1976D2;
+ }
+ """)
+ self.feedFinishBtn.setStyleSheet(button_base_style + """
+ QPushButton {
+ background-color: #4CAF50; /* 绿色:完成状态 */
+ color: white;
+ border-color: #388E3C;
+ }
+ """)
+
+ self.errorButton.setStyleSheet(button_base_style + """
+ QPushButton {
+ background-color: #E65100;
+ color: white;
+ border-color: #BF360C;
+ }
+ """)
+ self.cancelButton.setStyleSheet(button_base_style + """
+ QPushButton {
+ background-color: #C62828;
+ color: white;
+ border-color: #8E0000;
+ }
+ """)
+
+ # ---------------- 绑定按钮点击事件 ----------------
+ self.notFeedingBtn.clicked.connect(self.on_not_feeding_clicked)
+ self.feedingBtn.clicked.connect(self.on_feeding_clicked)
+ self.feedFinishBtn.clicked.connect(self.on_feed_finish_clicked)
+ self.errorButton.clicked.connect(self.on_error_clicked)
+ self.cancelButton.clicked.connect(self.on_cancel_clicked)
+
+ # ---------------- 初始禁用所有按钮(TCP连接后启用) ----------------
+ all_buttons = status_buttons + [self.errorButton, self.cancelButton]
+ for btn in all_buttons:
+ btn.setDisabled(True)
+
+ # ---------------- 按钮布局(5个按钮横向排列) ----------------
+ buttonLayout.addStretch(1)
+ buttonLayout.addWidget(self.notFeedingBtn)
+ buttonLayout.addWidget(self.feedingBtn)
+ buttonLayout.addWidget(self.feedFinishBtn)
+ buttonLayout.addWidget(self.errorButton)
+ buttonLayout.addWidget(self.cancelButton)
+ buttonLayout.addStretch(1)
+
+ parent_layout.addWidget(buttonContainer)
+
+ def _init_timers(self):
+ """初始化定时器(时间更新+状态模拟)"""
+ # 时间更新定时器(每秒更新一次)
+ self.time_timer = QTimer()
+ self.time_timer.timeout.connect(self.update_time)
+ self.time_timer.start(1000)
+
+ def get_current_time(self):
+ """获取格式化的当前时间"""
+ return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+
+ def get_timestamp(self):
+ """获取当前时间戳(秒级)"""
+ return datetime.now().strftime('%Y%m%d_%H%M%S_%f')[:-3]
+
+ def update_time(self):
+ """更新时间显示并触发重绘"""
+ self.current_datetime = self.get_current_time()
+ self.update() # 触发paintEvent重绘
+
+ def paintEvent(self, event):
+ """重写绘画事件,在右上角绘制日期时间文本"""
+ super().paintEvent(event) # 调用父类方法保持原有绘制
+
+ # 创建QPainter对象
+ painter = QPainter(self)
+ painter.setRenderHint(QPainter.RenderHint.TextAntialiasing) # 文本抗锯齿
+
+ # 设置字体
+ font = QFont("Arial", 12, QFont.Bold)
+ painter.setFont(font)
+
+ # 设置文本颜色
+ painter.setPen(QColor("#00FF9D"))
+
+ # 计算文本位置(右上角,留出边距)
+ text = f"🕒 {self.current_datetime}"
+ text_rect = painter.boundingRect(self.rect(), Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop, text)
+ x = self.width() - text_rect.width() - 15 # 右边距15px
+ y = 15 # 上边距15px
+
+ # 绘制文本
+ painter.drawText(x, y + text_rect.height(), text)
+
+ # --------------------
+ # 清空界面信息的通用方法
+ # --------------------
+ def _clear_ui_info(self):
+ """清空所有状态项的显示内容,并并设置指示灯颜色"""
+ for widget in self.statusWidgets:
+ widget['valueLabel'].setText("")
+ # 指示灯设为初始灰色(与_create_status_item中初始样式一致)
+ widget['indicator'].setStyleSheet("""
+ QLabel {
+ background-color: #9E9E9E;
+ border-radius: 10px;
+ border: 2px solid #555555;
+ }
+ """)
+ print("ℹ️ 界面信息已清空")
+
+ # ------------------
+ # TCP客户端核心功能
+ # ------------------
+ @Slot()
+ def _on_tcp_connected(self):
+ """TCP连接成功回调:启用所有操作按钮"""
+ self.is_tcp_connected = True
+ self.is_running = True
+ print(f"TCP连接成功:{self.tcp_server_host}:{self.tcp_server_port}")
+
+ # 连接成功后,向服务器发送“请求初始数据”指令
+ self._send_tcp_request("get_initial_data")
+
+ # 启用所有操作按钮(新增的3个状态按钮+原有2个功能按钮)
+ all_buttons = [self.notFeedingBtn, self.feedingBtn, self.feedFinishBtn, self.errorButton, self.cancelButton]
+ for btn in all_buttons:
+ btn.setDisabled(False)
+
+ @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}")
+
+ # 禁用所有操作按钮
+ all_buttons = [self.notFeedingBtn, self.feedingBtn, self.feedFinishBtn, self.errorButton, self.cancelButton]
+ for btn in all_buttons:
+ btn.setDisabled(True)
+
+ # 重置状态指示灯为“未连接”状态
+ for widget in self.statusWidgets:
+ widget['indicator'].setStyleSheet("""
+ QLabel {
+ background-color: #9E9E9E;
+ border-radius: 10px;
+ border: 2px solid #555555;
+ }
+ """)
+
+ @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_server_data = status_data
+ self._update_ui_from_data(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错误回调:禁用所有操作按钮"""
+ error_str = self.tcp_socket.errorString()
+ print(f"TCP错误:{error_str}")
+ self.is_tcp_connected = False
+ self.is_running = False
+
+ # 禁用所有操作按钮
+ all_buttons = [self.notFeedingBtn, self.feedingBtn, self.feedFinishBtn, self.errorButton, self.cancelButton]
+ for btn in all_buttons:
+ btn.setDisabled(True)
+
+ 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 _update_ui_from_data(self, data):
+ """根据TCP获取的数据更新界面状态"""
+ for widget in self.statusWidgets:
+ api_field = widget['api_field']
+ if api_field in data:
+ new_value = str(data[api_field])
+ widget['valueLabel'].setText(new_value)
+ self.set_indicator_status(widget, new_value)
+
+ # ------------------
+ # 状态指示灯逻辑
+ # ------------------
+ def set_indicator_status(self, widget, value):
+ """根据值设置状态指示灯颜色"""
+ if value and value != "未知" and value != "" and value != "None":
+ # 有效数据:绿色指示灯
+ widget['indicator'].setStyleSheet("""
+ QLabel {
+ background-color: #00E676;
+ border-radius: 10px;
+ border: 2px solid #00796B;
+ }
+ """)
+ else:
+ # 无效数据:红色指示灯
+ widget['indicator'].setStyleSheet("""
+ QLabel {
+ background-color: #FF5252;
+ border-radius: 10px;
+ border: 2px solid #C62828;
+ }
+ """)
+
+ # ------------------
+ # 按钮点击事件
+ # ------------------
+ def on_not_feeding_clicked(self):
+ """点击“未下料”:向服务端发送“未下料”状态指令"""
+ print("🔘 点击「未下料」按钮")
+ self._clear_ui_info()
+ self._send_tcp_request("not_feeding") # 指令名可根据服务端需求修改
+
+ def on_feeding_clicked(self):
+ """点击“下料中”:向服务端发送“下料中”状态指令"""
+ print("🔘 点击「下料中」按钮")
+ self._clear_ui_info()
+ self._send_tcp_request("feeding") # 指令名可根据服务端需求修改
+
+ def on_feed_finish_clicked(self):
+ """点击“下料完成”:向服务端发送“下料完成”状态指令"""
+ print("🔘 点击「下料完成」按钮")
+ self._clear_ui_info()
+ self._send_tcp_request("feed_finish") # 指令名可根据服务端需求修改
+
+ def on_error_clicked(self):
+ """点击“生产异常”:向服务端发送异常指令"""
+ print("🔘 点击「生产异常」按钮")
+ self._clear_ui_info()
+ self._send_tcp_request("production_error")
+
+ def on_cancel_clicked(self):
+ """点击“生产取消”:向服务端发送取消指令"""
+ print("🔘 点击「生产取消」按钮")
+ self._clear_ui_info()
+ self._send_tcp_request("cancel_feed")
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ window = StatusMonitor()
+ window.show()
+ sys.exit(app.exec())
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/operation_records/operation_record_20250919_205501_791.json b/zjsh_ui_sysytem/operation_records/operation_record_20250919_205501_791.json
new file mode 100644
index 0000000..6c2182f
--- /dev/null
+++ b/zjsh_ui_sysytem/operation_records/operation_record_20250919_205501_791.json
@@ -0,0 +1,17 @@
+{
+ "opration_button": "finish_feed",
+ "save_time": "2025-09-19 20:55:01",
+ "server_data": {
+ "task_id": "20250706-01",
+ "project_name": "18号线二期工程",
+ "section": "停车场工作井上行",
+ "slump": "50~70 mm",
+ "mix_ratio_id": "P2022=001",
+ "request_status": "请求中",
+ "material_grade": "C50P12",
+ "volume": "2m³",
+ "request_time": "10分钟后",
+ "car_status": "移动后",
+ "timestamp": "2025-09-19 20:54:55"
+ }
+}
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/operation_records/operation_record_20250930_095113_723.json b/zjsh_ui_sysytem/operation_records/operation_record_20250930_095113_723.json
new file mode 100644
index 0000000..af1c026
--- /dev/null
+++ b/zjsh_ui_sysytem/operation_records/operation_record_20250930_095113_723.json
@@ -0,0 +1,17 @@
+{
+ "opration_button": "finish_feed",
+ "save_time": "2025-09-30 09:51:13",
+ "server_data": {
+ "task_id": "20250706-01",
+ "project_name": "18号线二期工程",
+ "section": "停车场工作井上行",
+ "slump": "50~70 mm",
+ "mix_ratio_id": "P2022=001",
+ "request_status": "请求中",
+ "material_grade": "C50P12",
+ "volume": "2m³",
+ "request_time": "10分钟后",
+ "car_status": "移动后",
+ "timestamp": "2025-09-30 09:50:58"
+ }
+}
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/operation_records/operation_record_20251029_110426_514.json b/zjsh_ui_sysytem/operation_records/operation_record_20251029_110426_514.json
new file mode 100644
index 0000000..d7da5cb
--- /dev/null
+++ b/zjsh_ui_sysytem/operation_records/operation_record_20251029_110426_514.json
@@ -0,0 +1,17 @@
+{
+ "opration_button": "production_error",
+ "save_time": "2025-10-29 11:04:26",
+ "server_data": {
+ "task_id": "20250706-01",
+ "project_name": "18号线二期工程",
+ "section": "停车场工作井上行",
+ "slump": "50~70 mm",
+ "mix_ratio_id": "P2022=001",
+ "request_status": "请求中",
+ "material_grade": "C50P12",
+ "volume": "2m³",
+ "request_time": "10分钟后",
+ "car_status": "移动后",
+ "timestamp": "2025-10-29 11:02:19"
+ }
+}
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/operation_records/operation_record_20251029_202725_958.json b/zjsh_ui_sysytem/operation_records/operation_record_20251029_202725_958.json
new file mode 100644
index 0000000..650d487
--- /dev/null
+++ b/zjsh_ui_sysytem/operation_records/operation_record_20251029_202725_958.json
@@ -0,0 +1,17 @@
+{
+ "opration_button": "production_error",
+ "save_time": "2025-10-29 20:27:25",
+ "server_data": {
+ "task_id": "20250706-01",
+ "project_name": "18号线二期工程",
+ "section": "停车场工作井上行",
+ "slump": "50~70 mm",
+ "mix_ratio_id": "P2022=001",
+ "request_status": "请求中",
+ "material_grade": "C50P12",
+ "volume": "2m³",
+ "request_time": "10分钟后",
+ "car_status": "移动后",
+ "timestamp": "2025-10-29 20:27:12"
+ }
+}
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/operation_records/operation_record_20251030_105441_898.json b/zjsh_ui_sysytem/operation_records/operation_record_20251030_105441_898.json
new file mode 100644
index 0000000..9723132
--- /dev/null
+++ b/zjsh_ui_sysytem/operation_records/operation_record_20251030_105441_898.json
@@ -0,0 +1,17 @@
+{
+ "opration_button": "production_error",
+ "save_time": "2025-10-30 10:54:41",
+ "server_data": {
+ "task_id": "20250706-01",
+ "project_name": "18号线二期工程",
+ "section": "停车场工作井上行",
+ "slump": "50~70 mm",
+ "mix_ratio_id": "P2022=001",
+ "request_status": "请求中",
+ "material_grade": "C50P12",
+ "volume": "2m³",
+ "request_time": "10分钟后",
+ "car_status": "移动后",
+ "timestamp": "2025-10-30 10:54:40"
+ }
+}
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/operation_records/operation_record_20251030_105520_002.json b/zjsh_ui_sysytem/operation_records/operation_record_20251030_105520_002.json
new file mode 100644
index 0000000..b411941
--- /dev/null
+++ b/zjsh_ui_sysytem/operation_records/operation_record_20251030_105520_002.json
@@ -0,0 +1,17 @@
+{
+ "opration_button": "production_error",
+ "save_time": "2025-10-30 10:55:20",
+ "server_data": {
+ "task_id": "20250706-01",
+ "project_name": "18号线二期工程",
+ "section": "停车场工作井上行",
+ "slump": "50~70 mm",
+ "mix_ratio_id": "P2022=001",
+ "request_status": "请求中",
+ "material_grade": "C50P12",
+ "volume": "2m³",
+ "request_time": "10分钟后",
+ "car_status": "移动后",
+ "timestamp": "2025-10-30 10:55:18"
+ }
+}
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/operation_records/operation_record_20251030_105523_098.json b/zjsh_ui_sysytem/operation_records/operation_record_20251030_105523_098.json
new file mode 100644
index 0000000..6bb41c3
--- /dev/null
+++ b/zjsh_ui_sysytem/operation_records/operation_record_20251030_105523_098.json
@@ -0,0 +1,17 @@
+{
+ "opration_button": "cancel_feed",
+ "save_time": "2025-10-30 10:55:23",
+ "server_data": {
+ "task_id": "20250706-01",
+ "project_name": "18号线二期工程",
+ "section": "停车场工作井上行",
+ "slump": "50~70 mm",
+ "mix_ratio_id": "P2022=001",
+ "request_status": "请求中",
+ "material_grade": "C50P12",
+ "volume": "2m³",
+ "request_time": "10分钟后",
+ "car_status": "移动后",
+ "timestamp": "2025-10-30 10:55:18"
+ }
+}
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/operation_records/operation_record_20251030_170942_670.json b/zjsh_ui_sysytem/operation_records/operation_record_20251030_170942_670.json
new file mode 100644
index 0000000..1cf9e4e
--- /dev/null
+++ b/zjsh_ui_sysytem/operation_records/operation_record_20251030_170942_670.json
@@ -0,0 +1,8 @@
+{
+ "opration_button": "production_error",
+ "save_time": "2025-10-30 17:09:42",
+ "server_data": {
+ "erp_id": 82728,
+ "status": "数据已接收"
+ }
+}
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/operation_records/operation_record_20251030_171000_812.json b/zjsh_ui_sysytem/operation_records/operation_record_20251030_171000_812.json
new file mode 100644
index 0000000..473e13e
--- /dev/null
+++ b/zjsh_ui_sysytem/operation_records/operation_record_20251030_171000_812.json
@@ -0,0 +1,8 @@
+{
+ "opration_button": "production_error",
+ "save_time": "2025-10-30 17:10:00",
+ "server_data": {
+ "erp_id": 82728,
+ "status": "数据已接收"
+ }
+}
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/operation_records/operation_record_20251030_171004_403.json b/zjsh_ui_sysytem/operation_records/operation_record_20251030_171004_403.json
new file mode 100644
index 0000000..a1c9c40
--- /dev/null
+++ b/zjsh_ui_sysytem/operation_records/operation_record_20251030_171004_403.json
@@ -0,0 +1,8 @@
+{
+ "opration_button": "production_error",
+ "save_time": "2025-10-30 17:10:04",
+ "server_data": {
+ "erp_id": 82728,
+ "status": "数据已接收"
+ }
+}
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/operation_records/operation_record_20251030_171008_419.json b/zjsh_ui_sysytem/operation_records/operation_record_20251030_171008_419.json
new file mode 100644
index 0000000..98730f1
--- /dev/null
+++ b/zjsh_ui_sysytem/operation_records/operation_record_20251030_171008_419.json
@@ -0,0 +1,8 @@
+{
+ "opration_button": "production_error",
+ "save_time": "2025-10-30 17:10:08",
+ "server_data": {
+ "erp_id": 82728,
+ "status": "数据已接收"
+ }
+}
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/operation_records/operation_record_20251030_171022_493.json b/zjsh_ui_sysytem/operation_records/operation_record_20251030_171022_493.json
new file mode 100644
index 0000000..57cb891
--- /dev/null
+++ b/zjsh_ui_sysytem/operation_records/operation_record_20251030_171022_493.json
@@ -0,0 +1,8 @@
+{
+ "opration_button": "production_error",
+ "save_time": "2025-10-30 17:10:22",
+ "server_data": {
+ "erp_id": 82728,
+ "status": "数据已接收"
+ }
+}
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/operation_records/operation_record_20251030_171024_108.json b/zjsh_ui_sysytem/operation_records/operation_record_20251030_171024_108.json
new file mode 100644
index 0000000..edc6cec
--- /dev/null
+++ b/zjsh_ui_sysytem/operation_records/operation_record_20251030_171024_108.json
@@ -0,0 +1,8 @@
+{
+ "opration_button": "production_error",
+ "save_time": "2025-10-30 17:10:24",
+ "server_data": {
+ "erp_id": 82728,
+ "status": "数据已接收"
+ }
+}
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/operation_records/operation_record_20251030_182057_822.json b/zjsh_ui_sysytem/operation_records/operation_record_20251030_182057_822.json
new file mode 100644
index 0000000..ac4a7d1
--- /dev/null
+++ b/zjsh_ui_sysytem/operation_records/operation_record_20251030_182057_822.json
@@ -0,0 +1,8 @@
+{
+ "opration_button": "production_error",
+ "save_time": "2025-10-30 18:20:57",
+ "server_data": {
+ "erp_id": 82729,
+ "status": "数据已接收"
+ }
+}
\ No newline at end of file
diff --git a/zjsh_ui_sysytem/test.py b/zjsh_ui_sysytem/test.py
new file mode 100644
index 0000000..aa87988
--- /dev/null
+++ b/zjsh_ui_sysytem/test.py
@@ -0,0 +1,586 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+'''
+# @Time : 2025/9/10 11:29
+# @Author : reenrr
+# @Description : 在main函数的基础上添加了重连机制(为连接成功)
+# @File : test.py
+'''
+import sys
+from PySide6.QtWidgets import (
+ QApplication, QWidget, QVBoxLayout, QHBoxLayout,
+ QLabel, QFrame, QSizePolicy, QPushButton
+)
+from PySide6.QtCore import Qt, QTimer, QPoint, QSize, Slot
+from PySide6.QtGui import QFont, QPainter, QColor, QPen, QIcon
+from datetime import datetime
+from PySide6.QtNetwork import QTcpSocket, QAbstractSocket
+import json
+import os
+
+
+# -----------
+# 参数配置
+# -----------
+tcp_server_host = "127.0.0.1"
+tcp_server_port = 8888
+
+# 数据保存目录
+SAVE_DIR = "operation_records"
+MAX_RECONNECT = 3 # 最大重连次数
+RECONNECT_INTERVAL = 2000 # 重连间隔(毫秒)
+
+class StatusMonitor(QWidget):
+ """
+ 中交三航精准布料浇筑要料系统 - 主界面类(深色主题)
+ 使用TCP进行数据传输(客户端模型,与TCP服务器通信)
+ """
+ def __init__(self, parent=None):
+ """构造函数:初始化主界面的UI布局、控件和定时器"""
+ super().__init__(parent=parent)
+ self.is_running = False # 系统运行状态标记
+ self.current_datetime = self.get_current_time() # 当前日期时间
+ # 缓存服务端发送的最新JSON数据
+ self.latest_json_data = {}
+
+ # ---------------
+ # 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()
+
+ # 窗口基础设置
+ self.setWindowTitle("中交三航精准布料浇筑要料系统")
+ self.setGeometry(100, 100, 850, 500) # 设置窗口位置和大小
+ self.setStyleSheet("background-color: #121212;") # 窗口背景设为深黑色
+
+ # 初始化主布局(垂直布局)
+ self.mainLayout = QVBoxLayout(self)
+ self.mainLayout.setContentsMargins(10, 40, 10, 10) # 上边距留空用于显示日期
+ self.mainLayout.setSpacing(10) # 相邻控件的间距
+
+ # 初始化界面组件
+ self._init_title_label()
+ self._init_status_container()
+ self._init_timers()
+ self._init_save_dir()
+
+ # ----------------
+ # 客户端自动后自动连接服务端
+ # ----------------
+ print(f"客户端启动,自动连接服务端{self.tcp_server_host}:{self.tcp_server_port}...")
+ self._connect_to_server()
+
+ 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)
+
+ def _reconnect_to_server(self):
+ """重连执行函数:仅在未连接且未达最大次数时触发"""
+ if not self.is_tcp_connected and self.reconnect_count < MAX_RECONNECT:
+ self.reconnect_count += 1
+ print(f"第{self.reconnect_count}次重连(共{MAX_RECONNECT}次尝试)...")
+ self._connect_to_server()
+ elif self.reconnect_count >= MAX_RECONNECT:
+ self.reconnect_timer.stop() # 停止重连定时器
+ print(f"已达最大重连次数({MAX_RECONNECT}次),停止重连,请检查服务端状态")
+
+ def _init_save_dir(self):
+ """初始化数据保存目录"""
+ if not os.path.exists(SAVE_DIR):
+ os.makedirs(SAVE_DIR)
+ print(f"已创建数据保存目录:{os.path.abspath(SAVE_DIR)}")
+ else:
+ print(f"数据保存目录已存在:{os.path.abspath(SAVE_DIR)}")
+
+ 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)
+
+ # ------------------
+ # 界面初始化函数
+ # ------------------
+ def _init_title_label(self):
+ """初始化系统标题标签"""
+ titleLabel = QLabel("中交三航精准布料浇筑要料系统")
+ titleLabel.setStyleSheet("""
+ QLabel {
+ color: #00FF9D;
+ font-size: 20px;
+ font-weight: bold;
+ }
+ """)
+ titleLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ titleLabel.setFont(QFont("Microsoft YaHei", 24, QFont.Bold))
+ self.mainLayout.addWidget(titleLabel)
+
+ def _init_status_container(self):
+ """初始化核心状态监控容器(包含状态组和操作按钮)"""
+ self.bigContainer = QFrame()
+ self.bigContainer.setStyleSheet("""
+ QFrame {
+ background-color: #1E1E1E;
+ border: 2px solid #333333;
+ border-radius: 8px;
+ }
+ """)
+ self.bigContainer.setSizePolicy(
+ QSizePolicy.Policy.Expanding,
+ QSizePolicy.Policy.Expanding
+ )
+
+ containerLayout = QVBoxLayout(self.bigContainer)
+ containerLayout.setContentsMargins(20, 20, 20, 20)
+
+ self._init_status_groups(containerLayout)
+ self._init_operation_buttons(containerLayout)
+
+ self.mainLayout.addWidget(self.bigContainer, 1)
+
+ def _init_status_groups(self, parent_layout):
+ """初始化左右信息(各包含3个状态项)"""
+ statusWidget = QWidget()
+ statusLayout = QHBoxLayout(statusWidget)
+ statusLayout.setContentsMargins(0, 0, 0, 0)
+ statusLayout.setSpacing(30) # 减小中间空白间距(原30)
+
+ leftGroup = QWidget()
+ leftLayout = QVBoxLayout(leftGroup)
+ leftLayout.setSpacing(15)
+ leftLayout.setContentsMargins(30, 0, 0, 0) # 左边组左内边距设为20,增加左边留白
+
+ rightGroup = QWidget()
+ rightLayout = QVBoxLayout(rightGroup)
+ rightLayout.setSpacing(15)
+ rightLayout.setContentsMargins(0, 0, 30, 0) # 右边组右内边距设为20,增加右边留白(若需左边也留白,可设左内边距)
+
+ # 左边5个状态项及对应初始值
+ leftStatusInfo = [
+ {"name": "任务单号", "value": "", "api_field": "task_id"},
+ {"name": "工程名称", "value": "", "api_field": "project_name"},
+ {"name": "配比号", "value": "", "api_field": "produce_mix_id"}
+ ]
+ # 右边5个状态项及对应初始值
+ rightStatusInfo = [
+ {"name": "要料状态", "value": "", "api_field": "flag"},
+ {"name": "砼强度", "value": "", "api_field": "beton_grade"},
+ {"name": "要料方量", "value": "", "api_field": "adjusted_volume"},
+ ]
+ self.statusWidgets = []
+
+ # 处理左边状态项
+ for info in leftStatusInfo:
+ statusItem = self._create_status_item(info)
+ leftLayout.addWidget(statusItem)
+
+ # 处理右边状态项
+ for info in rightStatusInfo:
+ statusItem = self._create_status_item(info)
+ rightLayout.addWidget(statusItem)
+
+ statusLayout.addWidget(leftGroup)
+ statusLayout.addStretch(0) # 减小中间空白比例(原1)
+ statusLayout.addWidget(rightGroup)
+
+ parent_layout.addWidget(statusWidget)
+
+ def _create_status_item(self, info):
+ """创建单个状态项"""
+ statusItem = QFrame()
+ statusItem.setStyleSheet("""
+ QFrame {
+ background-color: #2D2D2D;
+ border: 1px solid #444444;
+ border-radius: 6px;
+ padding: 10px;
+ }
+ """)
+ statusItem.setFixedHeight(80)
+ statusItem.setFixedWidth(320) # 统一加长状态项宽度(原无固定宽度)
+
+ itemLayout = QHBoxLayout(statusItem)
+ itemLayout.setContentsMargins(10, 5, 10, 5)
+
+ # 状态指示灯
+ indicator = QLabel()
+ indicator.setFixedSize(20, 20)
+ indicator.setStyleSheet("""
+ QLabel {
+ background-color: #9E9E9E;
+ border-radius: 10px;
+ border: 2px solid #555555;
+ }
+ """)
+
+ # 状态名称标签
+ nameLabel = QLabel(info["name"])
+ nameLabel.setFixedWidth(100) # 加宽名称标签(原90)
+ nameLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ nameLabel.setStyleSheet("font-size: 14px; color: #FFFFFF;")
+ nameLabel.setFont(QFont("Microsoft YaHei", 12))
+
+ # 状态值标签
+ valueLabel = QLabel(info["value"])
+ valueLabel.setStyleSheet("""
+ QLabel {
+ font-size: 16px;
+ font-weight: bold;
+ color: #FFFFFF;
+ background-color: #2D2D2D;
+ border: none;
+ padding: 0px;
+ }
+ """)
+ valueLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ valueLabel.setMinimumWidth(150) # 加宽值标签(原80)
+
+ itemLayout.addWidget(indicator)
+ itemLayout.addSpacing(10)
+ itemLayout.addWidget(nameLabel)
+ itemLayout.addStretch()
+ itemLayout.addWidget(valueLabel)
+
+ self.statusWidgets.append({
+ 'indicator': indicator,
+ 'nameLabel': nameLabel,
+ 'valueLabel': valueLabel,
+ 'status': False,
+ 'initial_value': info["value"],
+ 'api_field': info["api_field"]
+ })
+
+ return statusItem
+
+ def _init_operation_buttons(self, parent_layout):
+ """初始化操作按钮(下料完成/生产异常/生产取消)"""
+ buttonContainer = QWidget()
+ buttonLayout = QHBoxLayout(buttonContainer)
+ buttonLayout.setContentsMargins(0, 20, 0, 0)
+ buttonLayout.setSpacing(30)
+
+ # 按钮图标(需替换为实际图标路径)
+ error_icon_path = "img.png"
+ cancel_icon_path = "img.png"
+
+ self.errorButton = QPushButton("生产异常")
+ self.cancelButton = QPushButton("生产取消")
+
+ # 设置按钮图标
+ self.errorButton.setIcon(QIcon(error_icon_path))
+ self.cancelButton.setIcon(QIcon(cancel_icon_path))
+
+ # 设置图标大小
+ self.errorButton.setIconSize(QSize(20, 20))
+ self.cancelButton.setIconSize(QSize(20, 20))
+
+ button_base_style = """
+ QPushButton {
+ font-size: 16px;
+ font-weight: bold;
+ padding: 10px 20px;
+ border-radius: 15px;
+ border: 1px solid;
+ min-width: 100px;
+ }
+ QPushButton:hover {
+ opacity: 0.9;
+ }
+ QPushButton:pressed {
+ opacity: 0.8;
+ }
+ """
+ self.errorButton.setStyleSheet(button_base_style + """
+ QPushButton {
+ background-color: #E65100;
+ color: white;
+ border-color: #BF360C;
+ }
+ """)
+
+ self.cancelButton.setStyleSheet(button_base_style + """
+ QPushButton {
+ background-color: #C62828;
+ color: white;
+ border-color: #8E0000;
+ }
+ """)
+
+ button_font = QFont("Microsoft YaHei", 12, QFont.Bold)
+ self.errorButton.setFont(button_font)
+ self.cancelButton.setFont(button_font)
+
+ self.errorButton.clicked.connect(self.on_error_clicked)
+ self.cancelButton.clicked.connect(self.on_cancel_clicked)
+
+ # 初始禁用“停止”和“异常”按钮(未连接时不可用)
+ self.errorButton.setDisabled(True)
+ self.cancelButton.setDisabled(True)
+
+ buttonLayout.addStretch(1)
+ buttonLayout.addWidget(self.errorButton)
+ buttonLayout.addWidget(self.cancelButton)
+ buttonLayout.addStretch(1)
+
+ parent_layout.addWidget(buttonContainer)
+
+ def _init_timers(self):
+ """初始化定时器(时间更新+状态模拟)"""
+ # 时间更新定时器(每秒更新一次)
+ self.time_timer = QTimer()
+ self.time_timer.timeout.connect(self.update_time)
+ self.time_timer.start(1000)
+
+ def get_current_time(self):
+ """获取格式化的当前时间"""
+ return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+
+ def get_timestamp(self):
+ """获取当前时间戳(秒级)"""
+ return datetime.now().strftime('%Y%m%d_%H%M%S_%f')[:-3]
+
+ def update_time(self):
+ """更新时间显示并触发重绘"""
+ self.current_datetime = self.get_current_time()
+ self.update() # 触发paintEvent重绘
+
+ def paintEvent(self, event):
+ """重写绘画事件,在右上角绘制日期时间文本"""
+ super().paintEvent(event) # 调用父类方法保持原有绘制
+
+ # 创建QPainter对象
+ painter = QPainter(self)
+ painter.setRenderHint(QPainter.RenderHint.TextAntialiasing) # 文本抗锯齿
+
+ # 设置字体
+ font = QFont("Arial", 12, QFont.Bold)
+ painter.setFont(font)
+
+ # 设置文本颜色
+ painter.setPen(QColor("#00FF9D"))
+
+ # 计算文本位置(右上角,留出边距)
+ text = f"🕒 {self.current_datetime}"
+ text_rect = painter.boundingRect(self.rect(), Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop, text)
+ x = self.width() - text_rect.width() - 15 # 右边距15px
+ y = 15 # 上边距15px
+
+ # 绘制文本
+ painter.drawText(x, y + text_rect.height(), text)
+
+ # ------------------
+ # 数据保存
+ # ------------------
+ def _save_data_to_file(self, button_name):
+ """
+ 将服务端数据+按钮操作信息保存到JSON文件
+
+ 参数:button_name:点击的按钮名称
+ """
+ # 1、检查是否有服务端数据
+ if not self.latest_server_data:
+ print(f"⚠️ 未收到服务端数据,无法保存「{button_name}」操作记录")
+ return
+
+ # 2、构建完整数据(服务端数据+按钮操作信息+存档时间)
+ save_data = {
+ "opration_button": button_name,
+ "save_time": self.get_current_time(),
+ "server_data":self.latest_server_data
+ }
+
+ # 3、生成唯一文件名(按时间戳命名)
+ file_name = f"operation_record_{self.get_timestamp()}.json"
+ file_path = os.path.join(SAVE_DIR, file_name)
+
+ # 4、写入JSON文件
+ try:
+ with open(file_path, "w", encoding="utf-8") as f:
+ json.dump(save_data, f, ensure_ascii=False, indent=4)
+ print(f"💾 保存「{button_name}」操作记录成功:{os.path.abspath(file_path)}")
+ except Exception as e:
+ print(f"💾 保存「{button_name}」操作记录失败:{str(e)}")
+
+ # --------------------
+ # 清空界面信息的通用方法
+ # --------------------
+ def _clear_ui_info(self):
+ """清空所有状态项的显示内容,并并设置指示灯颜色"""
+ for widget in self.statusWidgets:
+ widget['valueLabel'].setText("")
+ # 指示灯设为初始灰色(与_create_status_item中初始样式一致)
+ widget['indicator'].setStyleSheet("""
+ QLabel {
+ background-color: #9E9E9E;
+ border-radius: 10px;
+ border: 2px solid #555555;
+ }
+ """)
+ print("ℹ️ 界面信息已清空")
+
+ # ------------------
+ # 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._send_tcp_request("get_initial_data")
+
+ # 更新按钮状态:启用“生产异常”“生产取消”
+ self.errorButton.setDisabled(False)
+ self.cancelButton.setDisabled(False)
+
+ @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.errorButton.setDisabled(True)
+ self.cancelButton.setDisabled(True)
+
+ # 重置状态指示灯为“未连接”状态
+ for widget in self.statusWidgets:
+ widget['indicator'].setStyleSheet("""
+ QLabel {
+ background-color: #9E9E9E;
+ border-radius: 10px;
+ border: 2px solid #555555;
+ }
+ """)
+
+ @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)
+ print(status_data)
+ self.latest_server_data = status_data
+ self._update_ui_from_data(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.errorButton.setDisabled(True)
+ self.cancelButton.setDisabled(True)
+
+ # 首次连接失败时,启动重连定时器
+ if not self.has_connected_once and self.reconnect_count == 0:
+ print(f"将在{RECONNECT_INTERVAL / 1000}秒后启动重连(最多{MAX_RECONNECT}次)")
+ 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": "布料系统客户端",
+ "erp_id": self.latest_server_data.get('erp_id')
+ }) + "\n" # 增加换行符作为数据结束标识
+
+ # 发送请求数据
+ self.tcp_socket.write(request_data.encode("utf-8"))
+ print(f"TCP请求发送:{request_data.strip()}")
+
+ def _update_ui_from_data(self, data):
+ """根据TCP获取的数据更新界面状态"""
+ for widget in self.statusWidgets:
+ api_field = widget['api_field']
+ if api_field in data:
+ new_value = str(data[api_field])
+ widget['valueLabel'].setText(new_value)
+ self.set_indicator_status(widget, new_value)
+
+ # ------------------
+ # 状态指示灯逻辑
+ # ------------------
+ def set_indicator_status(self, widget, value):
+ """根据值设置状态指示灯颜色"""
+ if value and value != "未知" and value != "" and value != "None":
+ # 有效数据:绿色指示灯
+ widget['indicator'].setStyleSheet("""
+ QLabel {
+ background-color: #00E676;
+ border-radius: 10px;
+ border: 2px solid #00796B;
+ }
+ """)
+ else:
+ # 无效数据:红色指示灯
+ widget['indicator'].setStyleSheet("""
+ QLabel {
+ background-color: #FF5252;
+ border-radius: 10px;
+ border: 2px solid #C62828;
+ }
+ """)
+
+ # ------------------
+ # 按钮点击事件
+ # ------------------
+ def on_error_clicked(self):
+ """点击“生产异常”:向服务端发送异常指令"""
+ print("🔘 点击「生产异常」按钮")
+ self._clear_ui_info()
+ self._send_tcp_request("production_error")
+
+ def on_cancel_clicked(self):
+ """点击“生产取消”:向服务端发送取消指令"""
+ print("🔘 点击「生产取消」按钮")
+ self._clear_ui_info()
+ self._send_tcp_request("cancel_feed")
+
+if __name__ == "__main__":
+ app = QApplication(sys.argv)
+ window = StatusMonitor()
+ window.show()
+ sys.exit(app.exec())
\ No newline at end of file