feat: 修改上料按钮功能,实现弹框选择上料以及托盘等操作

This commit is contained in:
zhu-mengmeng 2025-06-16 17:17:55 +08:00
parent acb8e2f854
commit dfa09ea585
10 changed files with 835 additions and 105 deletions

86
apis/tary_api.py Normal file
View File

@ -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
}

View File

@ -9,6 +9,9 @@
}, },
"base_url":"http://192.168.0.133:8080" "base_url":"http://192.168.0.133:8080"
}, },
"apis": {
"get_tray_info": "/apjt/xcsc/tpda/getByTp_note/"
},
"database": { "database": {
"default": "sqlite", "default": "sqlite",
"sources": { "sources": {
@ -69,7 +72,5 @@
"stop_bits": 1, "stop_bits": 1,
"timeout": 1 "timeout": 1
} }
},"apis":{
"tpda":"/apjt/xcsc/tpda/list"
} }
} }

View File

@ -430,7 +430,7 @@ class InspectionDAO:
# TODO调用接口获取到工程号对应的其他信息比如材质规格后续完成 # TODO调用接口获取到工程号对应的其他信息比如材质规格后续完成
try: try:
sql = """ 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""" """
params = (order_id, tray_id, label_value, weight_value, net_weight_value, finish_time, datetime.now(), 'system', datetime.now(), 'system', False) params = (order_id, tray_id, label_value, weight_value, net_weight_value, finish_time, datetime.now(), 'system', datetime.now(), 'system', False)

Binary file not shown.

View File

@ -55,8 +55,8 @@ SET enum_values = '["A区", "B区", "C区", "D区"]'
WHERE name = 'fzd' AND is_deleted = FALSE; WHERE name = 'fzd' AND is_deleted = FALSE;
-- 包装记录表 -- 包装记录表
drop table if exists inspection_pack_data; drop table if exists wsbz_inspection_pack_data;
create table if not exists inspection_pack_data create table if not exists wsbz_inspection_pack_data
( (
-- --
order_id VARCHAR(50), order_id VARCHAR(50),

View File

@ -2,11 +2,12 @@ from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('localhost', port=5020) client = ModbusTcpClient('localhost', port=5020)
client.connect() 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=6, values=[1])
# client.write_registers(address=5, values=[16]) # 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) result = client.read_holding_registers(address=24, count=1)

285
ui/loading_dialog_ui.py Normal file
View File

@ -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)

View File

@ -1,6 +1,134 @@
import requests import requests
import json import json
from .config_loader import load_config from .config_loader import ConfigLoader
class ApiUtils: 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)

View File

@ -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()

View File

@ -53,6 +53,7 @@ class MainWindow(MainWindowUI):
self.corp_id = corp_id self.corp_id = corp_id
self.position_id = position_id self.position_id = position_id
self.init_seq = {} # 初始化轴包装的序号 self.init_seq = {} # 初始化轴包装的序号
self._loading_data_in_progress = False # 数据加载状态标志,防止循环调用
# 设置窗口标题 # 设置窗口标题
if user_name and corp_name: if user_name and corp_name:
@ -139,7 +140,7 @@ class MainWindow(MainWindowUI):
self.process_table.setContextMenuPolicy(Qt.CustomContextMenu) self.process_table.setContextMenuPolicy(Qt.CustomContextMenu)
# 加载未完成的检验数据 # 加载未完成的检验数据
self.load_finished_inspection_data() self._safe_load_data()
# 加载已完成检验数据 # 加载已完成检验数据
self.show_pack_item() self.show_pack_item()
@ -239,8 +240,8 @@ class MainWindow(MainWindowUI):
# 托盘号输入框回车和切换事件,触发未加载数据查询 # 托盘号输入框回车和切换事件,触发未加载数据查询
# QComboBox没有returnPressed信号只有currentTextChanged和activated信号 # QComboBox没有returnPressed信号只有currentTextChanged和activated信号
self.tray_edit.currentTextChanged.connect(self.load_finished_inspection_data) self.tray_edit.currentTextChanged.connect(self.handle_tray_changed)
self.tray_edit.activated.connect(self.load_finished_inspection_data) # 当用户选择一项时触发 self.tray_edit.activated.connect(self.handle_tray_changed) # 当用户选择一项时触发
# 连接按钮事件 # 连接按钮事件
self.input_button.clicked.connect(self.handle_input) self.input_button.clicked.connect(self.handle_input)
@ -289,7 +290,7 @@ class MainWindow(MainWindowUI):
self.update_inspection_columns() self.update_inspection_columns()
# 加载未完成的检验数据 # 加载未完成的检验数据
self.load_finished_inspection_data() self._safe_load_data()
# 只有在相机启用时处理相机显示 # 只有在相机启用时处理相机显示
if self.camera_enabled and hasattr(self, 'camera_display'): if self.camera_enabled and hasattr(self, 'camera_display'):
@ -328,68 +329,37 @@ class MainWindow(MainWindowUI):
def handle_input(self): def handle_input(self):
"""处理上料按钮点击事件""" """处理上料按钮点击事件"""
# 创建对话框 # 获取托盘号
dialog = QDialog(self) tray_id = self.tray_edit.currentText()
dialog.setWindowTitle("上料操作") if not tray_id:
dialog.setFixedSize(300, 200) 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("请选择拆垛层数:") self.serial_manager.start_keyboard_listener()
info_label.setFont(self.normal_font) logging.info("已在上料操作前启动键盘监听器")
layout.addWidget(info_label)
# 添加托盘类型选择 # 创建上料对话框
pallet_combo = QComboBox() from widgets.loading_dialog_widget import LoadingDialog
pallet_combo.setFont(self.normal_font) dialog = LoadingDialog(parent=self)
# 复制当前托盘类型选择器的内容
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)
# 显示对话框 # 显示对话框
result = dialog.exec() result = dialog.exec()
# 如果用户确认,则执行上料操作 # 如果用户点击确认按钮
if result == QDialog.Accepted: if result == QDialog.Accepted:
selected_type = pallet_combo.currentText() # TODO: 在这里添加上料操作的具体逻辑
logging.info(f"上料对话框已确认,托盘号: {self.tray_edit.currentText()}")
# 执行Modbus操作 pass
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)
def handle_output(self): def handle_output(self):
"""处理下料按钮点击事件""" """处理下料按钮点击事件"""
@ -771,7 +741,7 @@ class MainWindow(MainWindowUI):
QMessageBox.warning(self, "添加失败", f"添加新记录失败: {str(e)}") QMessageBox.warning(self, "添加失败", f"添加新记录失败: {str(e)}")
finally: finally:
# 重新加载数据确保UI显示正确 # 重新加载数据确保UI显示正确
self.load_finished_inspection_data() self._safe_load_data()
def limit_table_rows(self, max_rows): def limit_table_rows(self, max_rows):
"""限制表格最大行数 """限制表格最大行数
@ -902,7 +872,9 @@ class MainWindow(MainWindowUI):
self.statusBar().showMessage(f"处理检验数据失败: {str(e)[:50]}...", 3000) self.statusBar().showMessage(f"处理检验数据失败: {str(e)[:50]}...", 3000)
finally: finally:
# 延迟一段时间后再触发查询避免频繁刷新UI # 延迟一段时间后再触发查询避免频繁刷新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): def validate_inspection_value(self, config, value):
"""验证检验值是否有效 """验证检验值是否有效
@ -994,6 +966,7 @@ class MainWindow(MainWindowUI):
# 保存到数据库 # 保存到数据库
inspection_dao.save_inspection_data(order_id, data) inspection_dao.save_inspection_data(order_id, data)
# 注意不要在这里调用数据加载方法而是依靠信号和槽机制或QTimer安全地触发加载
except Exception as e: except Exception as e:
@ -1001,8 +974,22 @@ class MainWindow(MainWindowUI):
# 显示错误消息 # 显示错误消息
self.statusBar().showMessage(f"保存检验数据错误: {str(e)[:50]}...", 3000) 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): def load_finished_inspection_data(self):
"""加载未完成的检验数据并显示在表格中""" """加载未完成的检验数据并显示在表格中"""
# 注意此方法通常应通过_safe_load_data调用以防止循环
try: try:
# 使用InspectionDAO获取未完成的检验数据 # 使用InspectionDAO获取未完成的检验数据
from dao.inspection_dao import InspectionDAO from dao.inspection_dao import InspectionDAO
@ -1014,11 +1001,29 @@ class MainWindow(MainWindowUI):
# 使用get_inspection_data_unfinished获取未完成的数据 # 使用get_inspection_data_unfinished获取未完成的数据
unfinished_data = inspection_dao.get_inspection_data_unfinished(tray_id) 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: if not unfinished_data:
logging.info("没有未完成的检验数据") logging.info(f"托盘号 {tray_id} 没有未完成的检验数据")
# 清空表格现有数据行,但保留表头 # 确保表格完全清空,只保留表头行
while self.process_table.rowCount() > 2: self.process_table.setRowCount(2) # 只保留表头的两行
self.process_table.removeRow(2)
# 重新连接单元格变更信号
try:
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
except:
pass
# 加载包装记录
self.show_pack_item()
return return
logging.info(f"已加载未完成的检验数据,共 {len(unfinished_data)} 条记录") logging.info(f"已加载未完成的检验数据,共 {len(unfinished_data)} 条记录")
@ -1033,16 +1038,6 @@ class MainWindow(MainWindowUI):
if order_id not in orders_data: if order_id not in orders_data:
orders_data[order_id] = [] orders_data[order_id] = []
orders_data[order_id].append(data) 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行开始添加数据 # 添加数据到表格 - 从第3行开始添加数据
row_idx = 2 row_idx = 2
@ -1116,9 +1111,14 @@ class MainWindow(MainWindowUI):
QMessageBox.warning(self, "加载失败", f"加载未完成的检验数据失败: {str(e)}") QMessageBox.warning(self, "加载失败", f"加载未完成的检验数据失败: {str(e)}")
finally: 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): def load_finished_record_to_package_record(self, order_id, tray_id):
"""加载已完成检验数据到包装记录 """加载已完成检验数据到包装记录
@ -1168,8 +1168,13 @@ class MainWindow(MainWindowUI):
# 将数据写入到数据库表 inspection_pack_data # 将数据写入到数据库表 inspection_pack_data
inspection_dao.save_package_record(order_id, tray_id, label_value, weight_value,net_weight_value, finish_time) 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} 的检验数据添加到包装记录并回显") logging.info(f"已将工程号 {order_id} 托盘号 {tray_id} 的检验数据添加到包装记录并回显")
@ -1177,17 +1182,18 @@ class MainWindow(MainWindowUI):
logging.error(f"加载已完成检验数据到包装记录失败: {str(e)}") logging.error(f"加载已完成检验数据到包装记录失败: {str(e)}")
QMessageBox.warning(self, "加载失败", f"加载已完成检验数据到包装记录失败: {str(e)}") QMessageBox.warning(self, "加载失败", f"加载已完成检验数据到包装记录失败: {str(e)}")
def show_pack_item(self): def show_pack_item(self):
"""显示包装记录"""
try:
from dao.inspection_dao import InspectionDAO from dao.inspection_dao import InspectionDAO
inspection_dao = InspectionDAO() inspection_dao = InspectionDAO()
# 获取托盘号 # 获取托盘号
tray_id = self.tray_edit.currentText() tray_id = self.tray_edit.currentText()
# 读取已包装的记录信息,然后回显到 UI # 读取已包装的记录信息,然后回显到 UI
package_record = inspection_dao.get_package_record(tray_id) package_record = inspection_dao.get_package_record(tray_id)
# 清空包装记录表格,只保留表头 # 完全清空包装记录表格(包括所有行)
while self.record_table.rowCount() > 1: self.record_table.setRowCount(0)
self.record_table.removeRow(1)
# 断开包装记录表的信号连接(如果有) # 断开包装记录表的信号连接(如果有)
try: try:
@ -1201,7 +1207,7 @@ class MainWindow(MainWindowUI):
self.record_table.horizontalHeader().setSectionsMovable(False) self.record_table.horizontalHeader().setSectionsMovable(False)
self.record_table.horizontalHeader().setSectionsClickable(False) self.record_table.horizontalHeader().setSectionsClickable(False)
# 将第一行设置为表头,并固定在顶部 # 设置表头标签
self.record_table.setHorizontalHeaderLabels(["序号", "订单", "品名", "规格", "托号", "轴包装号", "毛重", "净重", "完成时间"]) self.record_table.setHorizontalHeaderLabels(["序号", "订单", "品名", "规格", "托号", "轴包装号", "毛重", "净重", "完成时间"])
self.record_table.horizontalHeader().setVisible(True) self.record_table.horizontalHeader().setVisible(True)
@ -1221,10 +1227,20 @@ class MainWindow(MainWindowUI):
self.record_table.setColumnWidth(col, width) self.record_table.setColumnWidth(col, width)
self.record_table.horizontalHeader().resizeSection(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): 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) self.record_table.insertRow(row_index)
# 设置包装记录数据 # 设置包装记录数据
@ -1278,11 +1294,14 @@ class MainWindow(MainWindowUI):
# 更新包装记录统计数据 # 更新包装记录统计数据
self.update_package_statistics() self.update_package_statistics()
except Exception as e:
logging.error(f"显示包装记录失败: {str(e)}")
QMessageBox.warning(self, "显示失败", f"显示包装记录失败: {str(e)}")
def update_package_statistics(self): def update_package_statistics(self):
"""更新包装记录统计数据""" """更新包装记录统计数据"""
try: try:
# 获取包装记录表的行数(不包括表头) # 获取包装记录表的行数
package_count = self.record_table.rowCount() - 1 package_count = self.record_table.rowCount()
# 更新任务表格中的已完成数量 # 更新任务表格中的已完成数量
completed_item = QTableWidgetItem(str(package_count)) completed_item = QTableWidgetItem(str(package_count))
@ -1291,7 +1310,7 @@ class MainWindow(MainWindowUI):
# 计算已完成公斤数(如果称重列有数值) # 计算已完成公斤数(如果称重列有数值)
completed_kg = 0 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) # 称重列 weight_item = self.record_table.item(row, 6) # 称重列
if weight_item and weight_item.text(): if weight_item and weight_item.text():
try: try:
@ -1651,7 +1670,7 @@ class MainWindow(MainWindowUI):
label_col = 2 + len(enabled_configs) 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: try:
@ -1868,7 +1887,7 @@ class MainWindow(MainWindowUI):
self.inspection_manager.delete_inspection_data(order_id, tray_id) 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}") logging.info(f"已删除当前在处理的数据: order_id: {order_id}, tray_id: {tray_id}")
""" """
try: try:
@ -1912,7 +1931,7 @@ class MainWindow(MainWindowUI):
self.init_seq[tray_id] = 1 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 self.init_seq[tray_id] += 1
# 保存贴标数据到数据库 # 保存贴标数据到数据库
@ -2134,9 +2153,11 @@ class MainWindow(MainWindowUI):
else: else:
item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色 item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色
# 保存到数据库 # 保存到数据库,但只在非加载状态下
tray_id = self.tray_edit.currentText() if not self._loading_data_in_progress:
self.save_inspection_data(order_id, tray_id, config_position, config_id, formatted_value, status) 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) self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
@ -2149,4 +2170,39 @@ class MainWindow(MainWindowUI):
try: try:
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed) self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
except: except:
pass 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)}")