diff --git a/apis/tary_api.py b/apis/tary_api.py new file mode 100644 index 0000000..b0ab575 --- /dev/null +++ b/apis/tary_api.py @@ -0,0 +1,86 @@ +from utils.api_utils import ApiUtils +import logging + +class TaryApi: + def __init__(self): + """初始化托盘API工具类""" + self.api_utils = ApiUtils() + + def get_tary_info(self, tary_code): + """ + 获取托盘信息 + + Args: + tary_code: 托盘编号 + + Returns: + dict: 托盘信息 + """ + try: + # API 配置中的键名 + api_key = "get_tray_info" + + # 将托盘号作为参数传递 + response = self.api_utils.get(api_key, params={"tp_note": tary_code}) + + # 记录API响应 + logging.info(f"托盘API响应: {response}") + + # 请求失败时返回空数据 + if not response.get("status", False): + return { + "success": False, + "message": "获取托盘信息失败", + "data": None + } + + # 成功时格式化数据 + if response.get("data"): + # 记录data的类型 + logging.info(f"数据类型: {type(response['data'])}, 数据内容: {response['data']}") + + # 如果data直接是对象,则使用该对象 + if isinstance(response["data"], dict): + logging.info("处理data为字典的情况") + tray_info = response["data"] + # 如果data是数组,并且有元素,则使用第一个元素 + elif isinstance(response["data"], list) and len(response["data"]) > 0: + logging.info("处理data为数组的情况") + tray_info = response["data"][0] + else: + logging.warning(f"数据格式不支持: {response['data']}") + return { + "success": False, + "message": "托盘数据格式不正确", + "data": None + } + + # 构建返回数据 + formatted_data = { + "tp_note": tray_info.get("tp_note", ""), # 托盘号 + "product_name": tray_info.get("zx_name", ""), # 产品名称 + "axis_type": tray_info.get("zx", ""), # 轴型 + "material": str(tray_info.get("cs", "")), # 托盘料 + "weight": str(tray_info.get("zl", "")), # 重量 + "quantity": "" # 数量先空下 + } + + return { + "success": True, + "message": "获取托盘信息成功", + "data": formatted_data + } + + # 数据为空 + return { + "success": False, + "message": "未找到托盘信息", + "data": None + } + except Exception as e: + logging.error(f"获取托盘信息异常: {str(e)}") + return { + "success": False, + "message": f"获取托盘信息异常: {str(e)}", + "data": None + } diff --git a/config/app_config.json b/config/app_config.json index 07d5037..a3985df 100644 --- a/config/app_config.json +++ b/config/app_config.json @@ -9,6 +9,9 @@ }, "base_url":"http://192.168.0.133:8080" }, + "apis": { + "get_tray_info": "/apjt/xcsc/tpda/getByTp_note/" + }, "database": { "default": "sqlite", "sources": { @@ -69,7 +72,5 @@ "stop_bits": 1, "timeout": 1 } - },"apis":{ - "tpda":"/apjt/xcsc/tpda/list" } } \ No newline at end of file diff --git a/dao/inspection_dao.py b/dao/inspection_dao.py index a32b4e0..8773de9 100644 --- a/dao/inspection_dao.py +++ b/dao/inspection_dao.py @@ -430,7 +430,7 @@ class InspectionDAO: # TODO:调用接口,获取到工程号对应的其他信息,比如材质,规格,后续完成 try: sql = """ - INSERT INTO inspection_pack_data (order_id, tray_id, axis_package_id, weight, net_weight, pack_time, create_time, create_by, update_time, update_by, is_deleted) + INSERT INTO wsbz_inspection_pack_data (order_id, tray_id, axis_package_id, weight, net_weight, pack_time, create_time, create_by, update_time, update_by, is_deleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """ params = (order_id, tray_id, label_value, weight_value, net_weight_value, finish_time, datetime.now(), 'system', datetime.now(), 'system', False) diff --git a/db/jtDB.db b/db/jtDB.db index e16eb71..74d7912 100644 Binary files a/db/jtDB.db and b/db/jtDB.db differ diff --git a/db/schema.sql b/db/schema.sql index a697b54..a20f36d 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -55,8 +55,8 @@ SET enum_values = '["A区", "B区", "C区", "D区"]' WHERE name = 'fzd' AND is_deleted = FALSE; -- 包装记录表 -drop table if exists inspection_pack_data; -create table if not exists inspection_pack_data +drop table if exists wsbz_inspection_pack_data; +create table if not exists wsbz_inspection_pack_data ( --订单号 order_id VARCHAR(50), diff --git a/from pymodbus.py b/from pymodbus.py index e35b5d8..257e237 100644 --- a/from pymodbus.py +++ b/from pymodbus.py @@ -2,11 +2,12 @@ from pymodbus.client import ModbusTcpClient client = ModbusTcpClient('localhost', port=5020) client.connect() -# client.write_registers(address=11, values=[110]) +# client.write_registers(address=11, values=[114]) # client.write_registers(address=6, values=[1]) # client.write_registers(address=5, values=[16]) -# client.write_registers(address=13, values=[1]) -client.write_registers(address=24, values=[1]) +# 贴标完成 +client.write_registers(address=13, values=[1]) +# client.write_registers(address=24, values=[1]) result = client.read_holding_registers(address=24, count=1) diff --git a/ui/loading_dialog_ui.py b/ui/loading_dialog_ui.py new file mode 100644 index 0000000..d346bff --- /dev/null +++ b/ui/loading_dialog_ui.py @@ -0,0 +1,285 @@ +from PySide6.QtWidgets import ( + QDialog, QLabel, QLineEdit, QComboBox, QPushButton, + QVBoxLayout, QHBoxLayout, QFrame +) +from PySide6.QtCore import Qt +from PySide6.QtGui import QFont + +class LoadingDialogUI(QDialog): + def __init__(self): + super().__init__() + self.setWindowTitle("上料操作") + self.setFixedSize(600, 300) + + # 设置字体 + self.normal_font = QFont("微软雅黑", 12) + + # 初始化UI + self.init_ui() + + def init_ui(self): + """初始化UI""" + # 主布局 + self.main_layout = QVBoxLayout(self) + self.main_layout.setContentsMargins(20, 20, 20, 20) + self.main_layout.setSpacing(0) # 移除布局间距 + + # 创建内容区域 + self.create_content_frame() + + # 创建按钮 + self.create_buttons() + + def create_content_frame(self): + """创建内容区域""" + # 创建一个带边框的容器 + container = QFrame() + container.setStyleSheet(""" + QFrame { + border: 1px solid #e0e0e0; + background-color: white; + } + """) + + # 容器的垂直布局 + container_layout = QVBoxLayout(container) + container_layout.setContentsMargins(0, 0, 0, 0) + container_layout.setSpacing(0) + + # 通用样式 + label_style = """ + QLabel { + background-color: #f5f5f5; + color: #333333; + font-weight: bold; + border: none; + border-right: 1px solid #e0e0e0; + border-bottom: 1px solid #e0e0e0; + padding: 0 8px; + } + """ + + input_style = """ + QLineEdit { + border: none; + border-right: 1px solid #e0e0e0; + border-bottom: 1px solid #e0e0e0; + background-color: white; + selection-background-color: #0078d4; + padding: 0 8px; + } + QLineEdit:focus { + background-color: #f8f8f8; + } + """ + + value_style = """ + QLabel { + background-color: white; + border: none; + border-right: 1px solid #e0e0e0; + border-bottom: 1px solid #e0e0e0; + padding: 0 8px; + } + """ + + # 第一行:订单号和产品 + row1 = QHBoxLayout() + row1.setSpacing(0) + + order_layout = QHBoxLayout() + order_layout.setSpacing(0) + self.order_label = QLabel("订单号") + self.order_label.setFont(self.normal_font) + self.order_label.setStyleSheet(label_style) + self.order_label.setFixedWidth(100) + self.order_label.setFixedHeight(45) + + self.order_input = QLineEdit() + self.order_input.setFont(self.normal_font) + self.order_input.setPlaceholderText("请扫描订单号") + self.order_input.setStyleSheet(input_style) + self.order_input.setFixedHeight(45) + + order_layout.addWidget(self.order_label) + order_layout.addWidget(self.order_input, 1) + + product_layout = QHBoxLayout() + product_layout.setSpacing(0) + self.product_label = QLabel("产品") + self.product_label.setFont(self.normal_font) + self.product_label.setStyleSheet(label_style) + self.product_label.setFixedWidth(100) + self.product_label.setFixedHeight(45) + + self.product_value = QLabel("") + self.product_value.setFont(self.normal_font) + self.product_value.setStyleSheet(value_style) + self.product_value.setFixedHeight(45) + + product_layout.addWidget(self.product_label) + product_layout.addWidget(self.product_value, 1) + + row1.addLayout(order_layout, 1) + row1.addLayout(product_layout, 1) + container_layout.addLayout(row1) + + # 第二行:托盘号 + row2 = QHBoxLayout() + row2.setSpacing(0) + + self.tray_label = QLabel("托盘号") + self.tray_label.setFont(self.normal_font) + self.tray_label.setStyleSheet(label_style) + self.tray_label.setFixedWidth(100) + self.tray_label.setFixedHeight(45) + + self.tray_input = QLineEdit() + self.tray_input.setFont(self.normal_font) + self.tray_input.setPlaceholderText("请扫描托盘号") + self.tray_input.setStyleSheet(input_style) + self.tray_input.setFixedHeight(45) + + row2.addWidget(self.tray_label) + row2.addWidget(self.tray_input, 1) + container_layout.addLayout(row2) + + # 第三行:轴型和托盘料 + row3 = QHBoxLayout() + row3.setSpacing(0) + + axis_layout = QHBoxLayout() + axis_layout.setSpacing(0) + self.axis_label = QLabel("轴型") + self.axis_label.setFont(self.normal_font) + self.axis_label.setStyleSheet(label_style) + self.axis_label.setFixedWidth(100) + self.axis_label.setFixedHeight(40) + + self.axis_value = QLabel("--") + self.axis_value.setFont(self.normal_font) + self.axis_value.setStyleSheet(value_style) + self.axis_value.setFixedHeight(40) + + axis_layout.addWidget(self.axis_label) + axis_layout.addWidget(self.axis_value, 1) + + material_layout = QHBoxLayout() + material_layout.setSpacing(0) + self.pallet_material_label = QLabel("托盘料") + self.pallet_material_label.setFont(self.normal_font) + self.pallet_material_label.setStyleSheet(label_style) + self.pallet_material_label.setFixedWidth(100) + self.pallet_material_label.setFixedHeight(40) + + self.pallet_material_value = QLabel("--") + self.pallet_material_value.setFont(self.normal_font) + self.pallet_material_value.setStyleSheet(value_style) + self.pallet_material_value.setFixedHeight(40) + + material_layout.addWidget(self.pallet_material_label) + material_layout.addWidget(self.pallet_material_value, 1) + + row3.addLayout(axis_layout, 1) + row3.addLayout(material_layout, 1) + container_layout.addLayout(row3) + + # 第四行:数量和重量 + row4 = QHBoxLayout() + row4.setSpacing(0) + + quantity_layout = QHBoxLayout() + quantity_layout.setSpacing(0) + self.quantity_label = QLabel("数量") + self.quantity_label.setFont(self.normal_font) + self.quantity_label.setStyleSheet(label_style) + self.quantity_label.setFixedWidth(100) + self.quantity_label.setFixedHeight(40) + + self.quantity_value = QLabel("--") + self.quantity_value.setFont(self.normal_font) + self.quantity_value.setStyleSheet(value_style) + self.quantity_value.setFixedHeight(40) + + quantity_layout.addWidget(self.quantity_label) + quantity_layout.addWidget(self.quantity_value, 1) + + weight_layout = QHBoxLayout() + weight_layout.setSpacing(0) + self.weight_label = QLabel("重量") + self.weight_label.setFont(self.normal_font) + self.weight_label.setStyleSheet(label_style) + self.weight_label.setFixedWidth(100) + self.weight_label.setFixedHeight(40) + + self.weight_value = QLabel("--") + self.weight_value.setFont(self.normal_font) + self.weight_value.setStyleSheet(value_style) + self.weight_value.setFixedHeight(40) + + weight_layout.addWidget(self.weight_label) + weight_layout.addWidget(self.weight_value, 1) + + row4.addLayout(quantity_layout, 1) + row4.addLayout(weight_layout, 1) + container_layout.addLayout(row4) + + # 添加弹性空间 + container_layout.addStretch() + + # 将容器添加到主布局 + self.main_layout.addWidget(container) + + def create_buttons(self): + """创建按钮""" + button_layout = QHBoxLayout() + button_layout.setContentsMargins(0, 10, 0, 0) + + self.confirm_button = QPushButton("确认") + self.confirm_button.setFont(self.normal_font) + self.confirm_button.setFixedSize(100, 35) + self.confirm_button.setStyleSheet(""" + QPushButton { + background-color: #0078d4; + color: white; + border: none; + border-radius: 4px; + padding: 8px 16px; + font-weight: bold; + } + QPushButton:hover { + background-color: #106ebe; + } + QPushButton:pressed { + background-color: #005a9e; + } + """) + + self.cancel_button = QPushButton("取消") + self.cancel_button.setFont(self.normal_font) + self.cancel_button.setFixedSize(100, 35) + self.cancel_button.setStyleSheet(""" + QPushButton { + background-color: white; + color: #333333; + border: 1px solid #e0e0e0; + border-radius: 4px; + padding: 8px 16px; + font-weight: bold; + } + QPushButton:hover { + background-color: #f5f5f5; + border-color: #bdbdbd; + } + QPushButton:pressed { + background-color: #e0e0e0; + border-color: #bdbdbd; + } + """) + + button_layout.addStretch() + button_layout.addWidget(self.confirm_button) + button_layout.addSpacing(30) # 添加30px的间距 + button_layout.addWidget(self.cancel_button) + + self.main_layout.addLayout(button_layout) \ No newline at end of file diff --git a/utils/api_utils.py b/utils/api_utils.py index c147d25..a1765eb 100644 --- a/utils/api_utils.py +++ b/utils/api_utils.py @@ -1,6 +1,134 @@ import requests import json -from .config_loader import load_config +from .config_loader import ConfigLoader class ApiUtils: + def __init__(self): + """初始化 API 工具类""" + self.config_loader = ConfigLoader.get_instance() + self.base_url = self.config_loader.get_value("app.base_url", "") + self.apis = self.config_loader.get_value("apis", {}) + + def get_full_url(self, url): + """ + 根据 API 键获取完整 URL + + Args: + url: API 配置中的键名 + + Returns: + str: 完整的 URL + """ + if url not in self.apis: + return None + + return f"{self.base_url}{self.apis[url]}" + + def request(self, method, url, params=None, data=None, json_data=None, headers=None): + """ + 发送 HTTP 请求 + + Args: + method: 请求方法,如 'GET', 'POST' 等 + url: API 配置中的键名 + params: URL 参数 + data: 表单数据 + json_data: JSON 数据 + headers: 请求头 + + Returns: + dict: 响应数据 + """ + full_url = self.get_full_url(url) + if not full_url: + return {"success": False, "message": f"未找到 API 配置: {url}"} + + # 处理托盘号参数 + # 如果是托盘查询接口,并且有tp_note参数,将其附加到URL末尾 + if url == "get_tray_info" and params and "tp_note" in params: + full_url = f"{full_url}{params['tp_note']}" + # 从params中移除tp_note,因为已经添加到URL中 + params = {k: v for k, v in params.items() if k != "tp_note"} + + default_headers = { + "Content-Type": "application/json" + } + + if headers: + default_headers.update(headers) + + try: + response = requests.request( + method=method, + url=full_url, + params=params, + data=data, + json=json_data, + headers=default_headers + ) + + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + return {"success": False, "message": f"请求异常: {str(e)}"} + except json.JSONDecodeError: + return {"success": False, "message": "响应数据不是有效的 JSON 格式"} + + def get(self, url, params=None, headers=None): + """ + 发送 GET 请求 + + Args: + url: API 配置中的键名 + params: URL 参数 + headers: 请求头 + + Returns: + dict: 响应数据 + """ + return self.request("GET", url, params=params, headers=headers) + + def post(self, url, json_data=None, data=None, headers=None): + """ + 发送 POST 请求 + + Args: + url: API 配置中的键名 + json_data: JSON 数据 + data: 表单数据 + headers: 请求头 + + Returns: + dict: 响应数据 + """ + return self.request("POST", url, data=data, json_data=json_data, headers=headers) + + def put(self, url, json_data=None, data=None, headers=None): + """ + 发送 PUT 请求 + + Args: + url: API 配置中的键名 + json_data: JSON 数据 + data: 表单数据 + headers: 请求头 + + Returns: + dict: 响应数据 + """ + return self.request("PUT", url, data=data, json_data=json_data, headers=headers) + + def delete(self, url, params=None, headers=None): + """ + 发送 DELETE 请求 + + Args: + url: API 配置中的键名 + params: URL 参数 + headers: 请求头 + + Returns: + dict: 响应数据 + """ + return self.request("DELETE", url, params=params, headers=headers) diff --git a/widgets/loading_dialog_widget.py b/widgets/loading_dialog_widget.py new file mode 100644 index 0000000..642afff --- /dev/null +++ b/widgets/loading_dialog_widget.py @@ -0,0 +1,173 @@ +from ui.loading_dialog_ui import LoadingDialogUI +from apis.tary_api import TaryApi +from PySide6.QtCore import Qt, Signal +from PySide6.QtWidgets import QMessageBox, QDialog +import logging + +class LoadingDialog(LoadingDialogUI): + # 定义一个信号,用于向主窗口传递托盘号 + tray_code_signal = Signal(str, str, str, str) + + def __init__(self, parent=None): + """初始化加载对话框""" + super().__init__() + self.parent = parent + + # 初始化API + self.tary_api = TaryApi() + + # 彻底禁用对话框的回车键关闭功能 + self.setModal(True) + # 禁用所有按钮的默认行为 + self.confirm_button.setAutoDefault(False) + self.confirm_button.setDefault(False) + self.cancel_button.setAutoDefault(False) + self.cancel_button.setDefault(False) + + # 设置对话框特性,按下Escape键才能关闭 + self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) + + # 绑定事件 + self.setup_connections() + + def setup_connections(self): + """设置事件连接""" + # 托盘号输入框回车事件触发查询 + self.tray_input.returnPressed.connect(self.handle_tray_return_pressed) + self.tray_input.editingFinished.connect(self.on_tray_query) + + # 确认按钮点击事件 + self.confirm_button.clicked.connect(self.accept) + + # 取消按钮点击事件 + self.cancel_button.clicked.connect(self.reject) + + def handle_tray_return_pressed(self): + """处理托盘输入框的回车事件""" + # 阻止事件传播 + logging.info("托盘输入框回车事件触发") + self.on_tray_query() + + # 阻止事件继续传播 + return True + + def on_tray_query(self): + """查询托盘信息""" + try: + tray_code = self.tray_input.text().strip() + if not tray_code: + return + + logging.info(f"查询托盘号: {tray_code}") + + # 调用API获取托盘信息 + response = self.tary_api.get_tary_info(tray_code) + + logging.info(f"托盘信息响应: {response}") + logging.info(f"response.success={response.get('success')}, response.data存在={response.get('data') is not None}") + + if response.get("success", False) and response.get("data"): + tray_data = response.get("data", {}) + logging.info(f"托盘数据: {tray_data}") + + # 显示托盘相关信息 - 只更新轴型、托盘料和重量,不更新订单号和产品 + axis_type = str(tray_data.get("axis_type", "--")) + material = str(tray_data.get("material", "--")) + weight = str(tray_data.get("weight", "--")) + + logging.info(f"显示托盘信息: 轴型={axis_type}, 托盘料={material}, 重量={weight}") + + # 只设置轴型、托盘料和重量字段,不设置产品名称 + self.axis_value.setText(axis_type) + self.pallet_material_value.setText(material) + self.quantity_value.setText("") # 数量为空 + self.weight_value.setText(f"{weight} kg") + + # 不再根据托盘号设置订单号 + # self.order_input.setText(tray_code) + + # 发送托盘号到主窗口 + from widgets.main_window import MainWindow + main_window = self.parent + if main_window and isinstance(main_window, MainWindow): + # 检查托盘号是否已存在 + existed = False + for i in range(main_window.tray_edit.count()): + if main_window.tray_edit.itemText(i) == tray_code: + existed = True + break + + # 如果不存在,则添加 + if not existed: + logging.info(f"添加托盘号到主窗口: {tray_code}") + main_window.tray_edit.addItem(tray_code) + + # 设置当前选中的托盘号 + main_window.tray_edit.setCurrentText(tray_code) + logging.info(f"设置主窗口当前托盘号: {tray_code}") + + # 成功获取信息后,将焦点设置到订单号输入框上 + self.order_input.setFocus() + else: + # 获取托盘信息失败 + error_msg = response.get("message", "获取托盘信息失败") + logging.warning(f"查询失败: {error_msg}") + QMessageBox.warning(self, "查询失败", error_msg) + except Exception as e: + logging.error(f"查询托盘信息异常: {str(e)}") + QMessageBox.critical(self, "查询异常", f"查询托盘信息时发生异常: {str(e)}") + + def keyPressEvent(self, event): + """重写键盘事件处理,防止回车关闭对话框""" + # 如果按下回车键 + if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter: + logging.info(f"捕获到回车键事件,当前焦点部件: {self.focusWidget()}") + + # 如果焦点在托盘输入框上,触发查询 + if self.focusWidget() == self.tray_input: + self.on_tray_query() + event.accept() # 消费掉这个事件 + return + + # 如果焦点在确认按钮上,则允许默认行为(确认) + if self.focusWidget() == self.confirm_button: + return super().keyPressEvent(event) + + # 其他情况下,阻止回车事件传播 + event.accept() # 消费掉这个事件 + return + + # 其他键位事件交给父类处理 + super().keyPressEvent(event) + + def accept(self): + """重写接受方法,确保在确认前所有数据都已处理""" + logging.info("确认按钮被点击或回车触发确认") + + # 确保主窗口启动了监听 + from widgets.main_window import MainWindow + main_window = self.parent + if main_window and isinstance(main_window, MainWindow): + # 确保主窗口已启动监听 + try: + if not hasattr(main_window, 'modbus_monitor') or not main_window.modbus_monitor.is_running(): + main_window.setup_modbus_monitor() + logging.info("已在LoadingDialog确认时启动Modbus监控") + + # 启动串口监听 + main_window.serial_manager.auto_open_configured_ports() + + # 启动键盘监听器 + main_window.serial_manager.start_keyboard_listener() + logging.info("已在LoadingDialog确认时启动键盘监听器") + except Exception as e: + logging.error(f"LoadingDialog确认时启动监听失败: {str(e)}") + + # 调用父类的accept方法关闭对话框 + super().accept() + + def reject(self): + """重写拒绝方法""" + logging.info("取消按钮被点击或ESC触发取消") + # 调用父类的reject方法关闭对话框 + super().reject() \ No newline at end of file diff --git a/widgets/main_window.py b/widgets/main_window.py index 4b17e7d..5903a0f 100644 --- a/widgets/main_window.py +++ b/widgets/main_window.py @@ -53,6 +53,7 @@ class MainWindow(MainWindowUI): self.corp_id = corp_id self.position_id = position_id self.init_seq = {} # 初始化轴包装的序号 + self._loading_data_in_progress = False # 数据加载状态标志,防止循环调用 # 设置窗口标题 if user_name and corp_name: @@ -139,7 +140,7 @@ class MainWindow(MainWindowUI): self.process_table.setContextMenuPolicy(Qt.CustomContextMenu) # 加载未完成的检验数据 - self.load_finished_inspection_data() + self._safe_load_data() # 加载已完成检验数据 self.show_pack_item() @@ -239,8 +240,8 @@ class MainWindow(MainWindowUI): # 托盘号输入框回车和切换事件,触发未加载数据查询 # QComboBox没有returnPressed信号,只有currentTextChanged和activated信号 - self.tray_edit.currentTextChanged.connect(self.load_finished_inspection_data) - self.tray_edit.activated.connect(self.load_finished_inspection_data) # 当用户选择一项时触发 + self.tray_edit.currentTextChanged.connect(self.handle_tray_changed) + self.tray_edit.activated.connect(self.handle_tray_changed) # 当用户选择一项时触发 # 连接按钮事件 self.input_button.clicked.connect(self.handle_input) @@ -289,7 +290,7 @@ class MainWindow(MainWindowUI): self.update_inspection_columns() # 加载未完成的检验数据 - self.load_finished_inspection_data() + self._safe_load_data() # 只有在相机启用时处理相机显示 if self.camera_enabled and hasattr(self, 'camera_display'): @@ -328,68 +329,37 @@ class MainWindow(MainWindowUI): def handle_input(self): """处理上料按钮点击事件""" - # 创建对话框 - dialog = QDialog(self) - dialog.setWindowTitle("上料操作") - dialog.setFixedSize(300, 200) + # 获取托盘号 + tray_id = self.tray_edit.currentText() + if not tray_id: + QMessageBox.warning(self, "提示", "请先选择或输入托盘号") + return + + # 启动监听(不论后续是否确认上料) + # 启动Modbus监控 + if not hasattr(self, 'modbus_monitor') or not self.modbus_monitor.is_running(): + self.setup_modbus_monitor() + logging.info("已在上料操作前启动Modbus监控") - # 对话框布局 - layout = QVBoxLayout(dialog) + # 启动串口监听 + self.serial_manager.auto_open_configured_ports() - # 添加提示信息 - info_label = QLabel("请选择拆垛层数:") - info_label.setFont(self.normal_font) - layout.addWidget(info_label) + # 启动键盘监听器 + self.serial_manager.start_keyboard_listener() + logging.info("已在上料操作前启动键盘监听器") - # 添加托盘类型选择 - pallet_combo = QComboBox() - pallet_combo.setFont(self.normal_font) - # 复制当前托盘类型选择器的内容 - for i in range(self.input_pallet_type_combo.count()): - pallet_combo.addItem(self.input_pallet_type_combo.itemText(i)) - layout.addWidget(pallet_combo) - - # 添加按钮 - button_layout = QHBoxLayout() - confirm_button = QPushButton("确认") - confirm_button.setFont(self.normal_font) - confirm_button.setStyleSheet("background-color: #e3f2fd; border: 1px solid #2196f3; padding: 8px 16px; font-weight: bold; border-radius: 4px;") - - cancel_button = QPushButton("取消") - cancel_button.setFont(self.normal_font) - cancel_button.setStyleSheet("padding: 8px 16px; font-weight: bold; border-radius: 4px;") - - button_layout.addStretch() - button_layout.addWidget(confirm_button) - button_layout.addWidget(cancel_button) - layout.addLayout(button_layout) - - # 连接按钮信号 - confirm_button.clicked.connect(dialog.accept) - cancel_button.clicked.connect(dialog.reject) + # 创建上料对话框 + from widgets.loading_dialog_widget import LoadingDialog + dialog = LoadingDialog(parent=self) # 显示对话框 result = dialog.exec() - # 如果用户确认,则执行上料操作 + # 如果用户点击确认按钮 if result == QDialog.Accepted: - selected_type = pallet_combo.currentText() - - # 执行Modbus操作 - modbus = ModbusUtils() - client = modbus.get_client() - try: - # 上料 D2 寄存器写入 1 ,D0 寄存器写入托盘类型 - if modbus.write_register_until_success(client, 2, 1) and modbus.write_register_until_success(client, 0, selected_type): - # 创建状态标签并显示在右上角 - self.show_operation_status("上料托盘", "input", selected_type) - else: - QMessageBox.information(self, "操作提示", "上料失败") - except Exception as e: - logging.error(f"上料操作失败: {str(e)}") - QMessageBox.critical(self, "错误", f"上料操作失败: {str(e)}") - finally: - modbus.close_client(client) + # TODO: 在这里添加上料操作的具体逻辑 + logging.info(f"上料对话框已确认,托盘号: {self.tray_edit.currentText()}") + pass def handle_output(self): """处理下料按钮点击事件""" @@ -771,7 +741,7 @@ class MainWindow(MainWindowUI): QMessageBox.warning(self, "添加失败", f"添加新记录失败: {str(e)}") finally: # 重新加载数据,确保UI显示正确 - self.load_finished_inspection_data() + self._safe_load_data() def limit_table_rows(self, max_rows): """限制表格最大行数 @@ -902,7 +872,9 @@ class MainWindow(MainWindowUI): self.statusBar().showMessage(f"处理检验数据失败: {str(e)[:50]}...", 3000) finally: # 延迟一段时间后再触发查询,避免频繁刷新UI - QTimer.singleShot(1000, self.load_finished_inspection_data) + # 但要避免在加载过程中触发新的加载 + if not self._loading_data_in_progress: + QTimer.singleShot(1000, self._safe_load_data) def validate_inspection_value(self, config, value): """验证检验值是否有效 @@ -994,6 +966,7 @@ class MainWindow(MainWindowUI): # 保存到数据库 inspection_dao.save_inspection_data(order_id, data) + # 注意:不要在这里调用数据加载方法,而是依靠信号和槽机制或QTimer安全地触发加载 except Exception as e: @@ -1001,8 +974,22 @@ class MainWindow(MainWindowUI): # 显示错误消息 self.statusBar().showMessage(f"保存检验数据错误: {str(e)[:50]}...", 3000) + def _safe_load_data(self): + """安全地加载数据,避免循环调用""" + if self._loading_data_in_progress: + # 如果已经在加载数据,不要再次触发 + logging.debug("已有数据加载正在进行,忽略此次请求") + return + + try: + self._loading_data_in_progress = True + self.load_finished_inspection_data() + finally: + self._loading_data_in_progress = False + def load_finished_inspection_data(self): """加载未完成的检验数据并显示在表格中""" + # 注意:此方法通常应通过_safe_load_data调用,以防止循环 try: # 使用InspectionDAO获取未完成的检验数据 from dao.inspection_dao import InspectionDAO @@ -1014,11 +1001,29 @@ class MainWindow(MainWindowUI): # 使用get_inspection_data_unfinished获取未完成的数据 unfinished_data = inspection_dao.get_inspection_data_unfinished(tray_id) + # 断开单元格变更信号,避免加载过程中触发保存 + try: + self.process_table.cellChanged.disconnect(self.handle_inspection_cell_changed) + except: + pass + + # 清空表格现有数据行,只保留表头 + while self.process_table.rowCount() > 2: + self.process_table.removeRow(2) + if not unfinished_data: - logging.info("没有未完成的检验数据") - # 清空表格现有数据行,但保留表头 - while self.process_table.rowCount() > 2: - self.process_table.removeRow(2) + logging.info(f"托盘号 {tray_id} 没有未完成的检验数据") + # 确保表格完全清空,只保留表头行 + self.process_table.setRowCount(2) # 只保留表头的两行 + + # 重新连接单元格变更信号 + try: + self.process_table.cellChanged.connect(self.handle_inspection_cell_changed) + except: + pass + + # 加载包装记录 + self.show_pack_item() return logging.info(f"已加载未完成的检验数据,共 {len(unfinished_data)} 条记录") @@ -1033,16 +1038,6 @@ class MainWindow(MainWindowUI): if order_id not in orders_data: orders_data[order_id] = [] orders_data[order_id].append(data) - - # 断开单元格变更信号,避免加载过程中触发保存 - try: - self.process_table.cellChanged.disconnect(self.handle_inspection_cell_changed) - except: - pass - - # 清空表格现有数据行,但保留表头 - while self.process_table.rowCount() > 2: - self.process_table.removeRow(2) # 添加数据到表格 - 从第3行开始添加数据 row_idx = 2 @@ -1116,9 +1111,14 @@ class MainWindow(MainWindowUI): QMessageBox.warning(self, "加载失败", f"加载未完成的检验数据失败: {str(e)}") finally: - # 加载包装记录 - self.show_pack_item() - + # 加载包装记录,但要避免循环调用 + # 设置一个标志,防止 show_pack_item 触发更多的数据加载 + if not hasattr(self, '_loading_data_in_progress'): + self._loading_data_in_progress = True + try: + self.show_pack_item() + finally: + self._loading_data_in_progress = False def load_finished_record_to_package_record(self, order_id, tray_id): """加载已完成检验数据到包装记录 @@ -1168,8 +1168,13 @@ class MainWindow(MainWindowUI): # 将数据写入到数据库表 inspection_pack_data inspection_dao.save_package_record(order_id, tray_id, label_value, weight_value,net_weight_value, finish_time) - # 回显数据 - self.show_pack_item() + # 回显数据,但避免循环调用 + if not hasattr(self, '_loading_data_in_progress'): + self._loading_data_in_progress = True + try: + self.show_pack_item() + finally: + self._loading_data_in_progress = False logging.info(f"已将工程号 {order_id} 托盘号 {tray_id} 的检验数据添加到包装记录并回显") @@ -1177,17 +1182,18 @@ class MainWindow(MainWindowUI): logging.error(f"加载已完成检验数据到包装记录失败: {str(e)}") QMessageBox.warning(self, "加载失败", f"加载已完成检验数据到包装记录失败: {str(e)}") def show_pack_item(self): + """显示包装记录""" + try: from dao.inspection_dao import InspectionDAO inspection_dao = InspectionDAO() - # 获取托盘号 + # 获取托盘号 tray_id = self.tray_edit.currentText() - # 读取已包装的记录信息,然后回显到 UI + # 读取已包装的记录信息,然后回显到 UI package_record = inspection_dao.get_package_record(tray_id) - # 清空包装记录表格,只保留表头 - while self.record_table.rowCount() > 1: - self.record_table.removeRow(1) + # 完全清空包装记录表格(包括所有行) + self.record_table.setRowCount(0) # 断开包装记录表的信号连接(如果有) try: @@ -1201,7 +1207,7 @@ class MainWindow(MainWindowUI): self.record_table.horizontalHeader().setSectionsMovable(False) self.record_table.horizontalHeader().setSectionsClickable(False) - # 将第一行设置为表头,并固定在顶部 + # 设置表头标签 self.record_table.setHorizontalHeaderLabels(["序号", "订单", "品名", "规格", "托号", "轴包装号", "毛重", "净重", "完成时间"]) self.record_table.horizontalHeader().setVisible(True) @@ -1221,10 +1227,20 @@ class MainWindow(MainWindowUI): self.record_table.setColumnWidth(col, width) self.record_table.horizontalHeader().resizeSection(col, width) + # 检查是否有包装记录数据 + if not package_record: + logging.info(f"托盘号 {tray_id} 没有包装记录数据") + # 表格已清空,不需要再设置行数 + # 更新包装记录统计数据 + self.update_package_statistics() + return + + logging.info(f"托盘号 {tray_id} 已加载包装记录,共 {len(package_record)} 条记录") + # 添加所有包装记录到表格 for index, item in enumerate(package_record): # 在包装记录表中添加新行 - row_index = index # 从第1行开始(索引为0),因为现在使用真正的表头 + row_index = self.record_table.rowCount() # 获取当前行数,从0开始 self.record_table.insertRow(row_index) # 设置包装记录数据 @@ -1278,11 +1294,14 @@ class MainWindow(MainWindowUI): # 更新包装记录统计数据 self.update_package_statistics() + except Exception as e: + logging.error(f"显示包装记录失败: {str(e)}") + QMessageBox.warning(self, "显示失败", f"显示包装记录失败: {str(e)}") def update_package_statistics(self): """更新包装记录统计数据""" try: - # 获取包装记录表的行数(不包括表头) - package_count = self.record_table.rowCount() - 1 + # 获取包装记录表的行数 + package_count = self.record_table.rowCount() # 更新任务表格中的已完成数量 completed_item = QTableWidgetItem(str(package_count)) @@ -1291,7 +1310,7 @@ class MainWindow(MainWindowUI): # 计算已完成公斤数(如果称重列有数值) completed_kg = 0 - for row in range(1, self.record_table.rowCount()): + for row in range(self.record_table.rowCount()): weight_item = self.record_table.item(row, 6) # 称重列 if weight_item and weight_item.text(): try: @@ -1651,7 +1670,7 @@ class MainWindow(MainWindowUI): label_col = 2 + len(enabled_configs) # 生成贴标号(托盘号+序号) - label_value = f"{tray_id}-{self.init_seq[tray_id]}" + label_value = f"{self.init_seq[tray_id]}" # 断开单元格变更信号,避免程序自动写入时触发 try: @@ -1868,7 +1887,7 @@ class MainWindow(MainWindowUI): self.inspection_manager.delete_inspection_data(order_id, tray_id) # 触发重新查询,更新数据 - self.load_finished_inspection_data() + self._safe_load_data() logging.info(f"已删除当前在处理的数据: order_id: {order_id}, tray_id: {tray_id}") """ try: @@ -1912,7 +1931,7 @@ class MainWindow(MainWindowUI): self.init_seq[tray_id] = 1 # 生成贴标号(托盘号+序号) - label_value = f"{tray_id}-{self.init_seq[tray_id]}-NG" + label_value = f"{self.init_seq[tray_id]}-NG" self.init_seq[tray_id] += 1 # 保存贴标数据到数据库 @@ -2134,9 +2153,11 @@ class MainWindow(MainWindowUI): else: item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色 - # 保存到数据库 - tray_id = self.tray_edit.currentText() - self.save_inspection_data(order_id, tray_id, config_position, config_id, formatted_value, status) + # 保存到数据库,但只在非加载状态下 + if not self._loading_data_in_progress: + tray_id = self.tray_edit.currentText() + self.save_inspection_data(order_id, tray_id, config_position, config_id, formatted_value, status) + # 不需要在这里主动触发数据重新加载,因为handle_inspection_cell_changed会处理 # 重新连接信号 self.process_table.cellChanged.connect(self.handle_inspection_cell_changed) @@ -2149,4 +2170,39 @@ class MainWindow(MainWindowUI): try: self.process_table.cellChanged.connect(self.handle_inspection_cell_changed) except: - pass \ No newline at end of file + pass + + def handle_tray_changed(self): + """处理托盘号变更事件,启动监听并加载数据""" + try: + tray_id = self.tray_edit.currentText() + if tray_id: + logging.info(f"托盘号变更为 {tray_id},启动监听") + + # 确保启动Modbus监控 + if not hasattr(self, 'modbus_monitor') or not self.modbus_monitor.is_running(): + try: + self.setup_modbus_monitor() + logging.info("已在托盘号变更时启动Modbus监控") + except Exception as e: + logging.error(f"托盘号变更时启动Modbus监控失败: {str(e)}") + + # 确保启动串口监听 + try: + self.serial_manager.auto_open_configured_ports() + # 启动键盘监听器 + self.serial_manager.start_keyboard_listener() + logging.info("已在托盘号变更时启动串口和键盘监听器") + except Exception as e: + logging.error(f"托盘号变更时启动串口监听失败: {str(e)}") + + # 初始化托盘号对应的序号(如果不存在) + if tray_id not in self.init_seq: + self.init_seq[tray_id] = 1 + logging.info(f"初始化托盘号 {tray_id} 的序号为 1") + + # 加载数据 + self._safe_load_data() + + except Exception as e: + logging.error(f"处理托盘号变更失败: {str(e)}") \ No newline at end of file