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