2025-06-07 10:45:09 +08:00
|
|
|
|
import os
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import logging
|
2025-06-07 14:10:32 +08:00
|
|
|
|
import json
|
2025-06-07 10:45:09 +08:00
|
|
|
|
from datetime import datetime
|
2025-06-08 01:24:27 +08:00
|
|
|
|
from pathlib import Path
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
# 导入PySide6
|
2025-06-08 01:24:27 +08:00
|
|
|
|
from PySide6.QtWidgets import (
|
|
|
|
|
|
QWidget, QMessageBox, QTableWidgetItem, QStackedWidget, QLabel, QMainWindow,
|
|
|
|
|
|
QTableWidget, QMenu
|
|
|
|
|
|
)
|
2025-06-07 10:45:09 +08:00
|
|
|
|
from PySide6.QtCore import Qt, QTimer
|
|
|
|
|
|
from PySide6.QtGui import QBrush, QColor
|
|
|
|
|
|
|
|
|
|
|
|
# 导入UI
|
|
|
|
|
|
from ui.main_window_ui import MainWindowUI
|
|
|
|
|
|
# 导入相机显示组件和设置组件
|
|
|
|
|
|
from widgets.camera_display_widget import CameraDisplayWidget
|
|
|
|
|
|
from widgets.camera_settings_widget import CameraSettingsWidget
|
|
|
|
|
|
|
2025-06-07 16:44:27 +08:00
|
|
|
|
# 导入检验配置管理器
|
|
|
|
|
|
from utils.inspection_config_manager import InspectionConfigManager
|
|
|
|
|
|
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
class MainWindow(MainWindowUI):
|
|
|
|
|
|
"""主窗口"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, user_id=None, user_name=None, corp_name=None, corp_id=None, position_id=None):
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
self.user_id = user_id
|
|
|
|
|
|
self.user_name = user_name
|
|
|
|
|
|
self.corp_name = corp_name
|
|
|
|
|
|
self.corp_id = corp_id
|
|
|
|
|
|
self.position_id = position_id
|
|
|
|
|
|
|
|
|
|
|
|
# 设置窗口标题
|
|
|
|
|
|
if user_name and corp_name:
|
|
|
|
|
|
self.setWindowTitle(f"腾智微丝产线包装系统 - {user_name} ({corp_name})")
|
|
|
|
|
|
|
2025-06-07 14:10:32 +08:00
|
|
|
|
# 加载配置文件
|
|
|
|
|
|
self.config = self.load_config()
|
|
|
|
|
|
self.camera_enabled = self.config.get('camera', {}).get('enabled', False)
|
|
|
|
|
|
|
2025-06-07 16:44:27 +08:00
|
|
|
|
# 初始化检验配置管理器
|
|
|
|
|
|
self.inspection_manager = InspectionConfigManager.get_instance()
|
|
|
|
|
|
|
2025-06-07 14:10:32 +08:00
|
|
|
|
# 只有在相机启用时创建相机显示组件
|
|
|
|
|
|
if self.camera_enabled:
|
|
|
|
|
|
# 创建相机显示组件并添加到上料区
|
|
|
|
|
|
self.camera_display = CameraDisplayWidget()
|
|
|
|
|
|
self.material_content_layout.addWidget(self.camera_display)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 在上料区添加占位标签
|
|
|
|
|
|
self.material_placeholder = QLabel("相机功能已禁用")
|
|
|
|
|
|
self.material_placeholder.setAlignment(Qt.AlignCenter)
|
|
|
|
|
|
self.material_placeholder.setStyleSheet("color: #888888; background-color: #f0f0f0;")
|
|
|
|
|
|
self.material_content_layout.addWidget(self.material_placeholder)
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
# 为下料区添加占位标签,确保它保持为空
|
|
|
|
|
|
self.output_placeholder = QLabel("下料区 - 暂无内容")
|
|
|
|
|
|
self.output_placeholder.setAlignment(Qt.AlignCenter)
|
|
|
|
|
|
self.output_placeholder.setStyleSheet("color: #888888; background-color: #f0f0f0;")
|
|
|
|
|
|
self.output_content_layout.addWidget(self.output_placeholder)
|
|
|
|
|
|
|
|
|
|
|
|
# 创建堆叠部件
|
|
|
|
|
|
self.stacked_widget = QStackedWidget()
|
|
|
|
|
|
self.stacked_widget.addWidget(self.central_widget) # 主页面
|
|
|
|
|
|
|
|
|
|
|
|
# 不在这里直接初始化相机设置组件
|
|
|
|
|
|
# 延迟创建,保证创建的时候SettingsUI的所有控件都已经准备好
|
|
|
|
|
|
self.camera_settings = None
|
|
|
|
|
|
|
|
|
|
|
|
# 设置中央部件为堆叠部件
|
|
|
|
|
|
self.setCentralWidget(self.stacked_widget)
|
|
|
|
|
|
|
|
|
|
|
|
# 连接信号和槽
|
2025-06-07 14:10:32 +08:00
|
|
|
|
self.connect_signals()
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
# 默认显示主页面
|
|
|
|
|
|
self.stacked_widget.setCurrentIndex(0)
|
|
|
|
|
|
|
|
|
|
|
|
# 初始化数据
|
|
|
|
|
|
self.initialize_data()
|
|
|
|
|
|
|
2025-06-07 16:44:27 +08:00
|
|
|
|
# 配置检验列 - 使用检验配置管理器获取启用的列数和标题
|
|
|
|
|
|
self.update_inspection_columns()
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
2025-06-08 01:24:27 +08:00
|
|
|
|
# 设置表格上下文菜单
|
|
|
|
|
|
self.process_table.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
|
|
|
|
self.process_table.customContextMenuRequested.connect(self.show_table_context_menu)
|
|
|
|
|
|
|
|
|
|
|
|
# 加载未完成的检验数据
|
|
|
|
|
|
self.load_unfinished_inspection_data()
|
|
|
|
|
|
|
2025-06-07 10:45:09 +08:00
|
|
|
|
logging.info(f"主窗口已创建,用户: {user_name}")
|
|
|
|
|
|
|
2025-06-07 14:10:32 +08:00
|
|
|
|
def load_config(self):
|
|
|
|
|
|
"""加载配置文件"""
|
|
|
|
|
|
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "config", "app_config.json")
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(config_path, 'r', encoding='utf-8') as f:
|
|
|
|
|
|
config = json.load(f)
|
|
|
|
|
|
logging.info(f"已加载配置文件: {config_path}")
|
|
|
|
|
|
return config
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"加载配置文件失败: {e}")
|
|
|
|
|
|
return {}
|
|
|
|
|
|
|
2025-06-07 10:45:09 +08:00
|
|
|
|
def connect_signals(self):
|
|
|
|
|
|
# 连接菜单动作
|
|
|
|
|
|
self.main_action.triggered.connect(self.show_main_page)
|
|
|
|
|
|
self.settings_action.triggered.connect(self.show_settings_page)
|
|
|
|
|
|
|
2025-06-07 16:44:27 +08:00
|
|
|
|
# 工程号输入框回车事件
|
|
|
|
|
|
self.order_edit.returnPressed.connect(self.handle_order_enter)
|
|
|
|
|
|
|
2025-06-09 01:16:49 +08:00
|
|
|
|
# 托盘号输入框回车和切换事件,触发未加载数据查询
|
|
|
|
|
|
# QComboBox没有returnPressed信号,只有currentTextChanged和activated信号
|
|
|
|
|
|
self.tray_edit.currentTextChanged.connect(self.load_unfinished_inspection_data)
|
|
|
|
|
|
self.tray_edit.activated.connect(self.load_unfinished_inspection_data) # 当用户选择一项时触发
|
|
|
|
|
|
|
2025-06-07 10:45:09 +08:00
|
|
|
|
# 连接按钮事件
|
|
|
|
|
|
self.input_button.clicked.connect(self.handle_input)
|
|
|
|
|
|
self.output_button.clicked.connect(self.handle_output)
|
|
|
|
|
|
self.start_button.clicked.connect(self.handle_start)
|
|
|
|
|
|
self.stop_button.clicked.connect(self.handle_stop)
|
|
|
|
|
|
|
2025-06-07 14:10:32 +08:00
|
|
|
|
# 只有在相机启用时连接相机信号
|
|
|
|
|
|
if self.camera_enabled and hasattr(self, 'camera_display'):
|
|
|
|
|
|
self.camera_display.signal_camera_status.connect(self.handle_camera_status)
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
def initialize_data(self):
|
|
|
|
|
|
"""初始化界面数据"""
|
|
|
|
|
|
# 设置订单和批号
|
|
|
|
|
|
self.order_edit.setText("ORD-2025-001")
|
2025-06-07 14:10:32 +08:00
|
|
|
|
self.tray_edit.setCurrentText("托盘1")
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
# 初始化项目表格数据示例
|
|
|
|
|
|
project_data = [
|
|
|
|
|
|
["100", "50", "200", "95%"],
|
|
|
|
|
|
["3000", "1500", "6000", "92%"],
|
|
|
|
|
|
["36000", "18000", "72000", "90%"],
|
|
|
|
|
|
["120000", "60000", "240000", "91%"]
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
for row in range(4):
|
2025-06-07 14:10:32 +08:00
|
|
|
|
for col in range(0, 4): # 从第2列开始(跳过项目列)
|
|
|
|
|
|
item = QTableWidgetItem(project_data[row][col])
|
2025-06-07 10:45:09 +08:00
|
|
|
|
# item.setTextAlignment(Qt.AlignCenter) # 设置文本居中对齐
|
|
|
|
|
|
self.project_table.setItem(row, col, item)
|
|
|
|
|
|
|
|
|
|
|
|
# 初始化任务表格数据示例 - 注意:现在表格有3行,第0行是一级标题,第1行是二级标题,第2行是数据行
|
|
|
|
|
|
# 数据应该填充在第2行(索引为2)
|
|
|
|
|
|
data = ["200", "95", "0", "0"] # 分别对应:总生产数量、总生产公斤、已完成数量、已完成公斤
|
|
|
|
|
|
for col, value in enumerate(data):
|
|
|
|
|
|
item = QTableWidgetItem(value)
|
|
|
|
|
|
item.setTextAlignment(Qt.AlignCenter) # 设置文本居中对齐
|
|
|
|
|
|
self.task_table.setItem(2, col, item)
|
|
|
|
|
|
|
2025-06-07 16:44:27 +08:00
|
|
|
|
def update_inspection_columns(self):
|
|
|
|
|
|
"""更新检验列配置 - 使用检验配置管理器获取启用的列数和标题"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 获取已启用的检验配置
|
|
|
|
|
|
enabled_configs = self.inspection_manager.get_enabled_configs()
|
|
|
|
|
|
|
|
|
|
|
|
# 获取启用的列数
|
|
|
|
|
|
column_count = len(enabled_configs)
|
|
|
|
|
|
if column_count == 0:
|
|
|
|
|
|
# 如果没有启用的列,至少显示一列
|
|
|
|
|
|
column_count = 1
|
|
|
|
|
|
headers = ["检验项"]
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 如果有启用的列,使用配置的标题
|
|
|
|
|
|
headers = [config['display_name'] for config in enabled_configs]
|
|
|
|
|
|
|
|
|
|
|
|
# 设置检验列
|
|
|
|
|
|
self.set_inspection_columns(column_count, headers)
|
|
|
|
|
|
logging.info(f"已更新检验列配置:{column_count}列, 标题: {headers}")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"更新检验列配置失败: {str(e)}")
|
|
|
|
|
|
# 如果更新失败,使用默认配置
|
|
|
|
|
|
self.set_inspection_columns(1, ["检验项"])
|
|
|
|
|
|
|
2025-06-07 10:45:09 +08:00
|
|
|
|
def show_main_page(self):
|
|
|
|
|
|
self.stacked_widget.setCurrentWidget(self.central_widget)
|
|
|
|
|
|
|
2025-06-07 16:44:27 +08:00
|
|
|
|
# 更新检验列配置
|
|
|
|
|
|
self.update_inspection_columns()
|
|
|
|
|
|
|
2025-06-08 01:24:27 +08:00
|
|
|
|
# 加载未完成的检验数据
|
|
|
|
|
|
self.load_unfinished_inspection_data()
|
|
|
|
|
|
|
2025-06-07 14:10:32 +08:00
|
|
|
|
# 只有在相机启用时处理相机显示
|
|
|
|
|
|
if self.camera_enabled and hasattr(self, 'camera_display'):
|
|
|
|
|
|
# 如果相机已连接,直接开始显示相机画面
|
|
|
|
|
|
if self.camera_display.camera_manager.isOpen:
|
|
|
|
|
|
if not self.camera_display.camera_manager.isGrabbing:
|
|
|
|
|
|
self.camera_display.start_display()
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
logging.info("显示主页面")
|
|
|
|
|
|
|
|
|
|
|
|
def show_settings_page(self):
|
2025-06-07 16:44:27 +08:00
|
|
|
|
"""显示设置页面"""
|
|
|
|
|
|
# 延迟创建设置组件
|
|
|
|
|
|
if not hasattr(self, 'settings_widget'):
|
|
|
|
|
|
from widgets.settings_widget import SettingsWidget
|
|
|
|
|
|
self.settings_widget = SettingsWidget(self)
|
|
|
|
|
|
self.stacked_widget.addWidget(self.settings_widget)
|
2025-06-07 14:10:32 +08:00
|
|
|
|
|
2025-06-07 16:44:27 +08:00
|
|
|
|
# 切换到设置页面
|
|
|
|
|
|
self.stacked_widget.setCurrentWidget(self.settings_widget)
|
2025-06-07 10:45:09 +08:00
|
|
|
|
logging.info("显示设置页面")
|
|
|
|
|
|
|
|
|
|
|
|
def handle_input(self):
|
|
|
|
|
|
"""处理上料按钮点击事件"""
|
|
|
|
|
|
logging.info("上料按钮被点击")
|
|
|
|
|
|
|
|
|
|
|
|
QMessageBox.information(self, "操作提示", "开始上料操作")
|
|
|
|
|
|
# 这里添加上料相关的业务逻辑
|
|
|
|
|
|
|
|
|
|
|
|
def handle_output(self):
|
|
|
|
|
|
"""处理下料按钮点击事件"""
|
|
|
|
|
|
logging.info("下料按钮被点击")
|
|
|
|
|
|
QMessageBox.information(self, "操作提示", "开始下料操作")
|
|
|
|
|
|
# 这里添加下料相关的业务逻辑
|
|
|
|
|
|
|
|
|
|
|
|
def handle_start(self):
|
|
|
|
|
|
"""处理开始按钮点击事件"""
|
|
|
|
|
|
logging.info("开始按钮被点击")
|
|
|
|
|
|
|
2025-06-07 14:10:32 +08:00
|
|
|
|
# 只有在相机启用时处理相机显示
|
|
|
|
|
|
if self.camera_enabled and hasattr(self, 'camera_display'):
|
|
|
|
|
|
# 开始显示相机画面
|
|
|
|
|
|
if self.camera_display.camera_manager.isOpen:
|
|
|
|
|
|
self.camera_display.start_display()
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
QMessageBox.information(self, "操作提示", "生产线已启动")
|
|
|
|
|
|
# 这里添加启动生产线的业务逻辑
|
|
|
|
|
|
|
|
|
|
|
|
def handle_stop(self):
|
|
|
|
|
|
"""处理暂停按钮点击事件"""
|
|
|
|
|
|
logging.info("暂停按钮被点击")
|
|
|
|
|
|
|
2025-06-07 14:10:32 +08:00
|
|
|
|
# 只有在相机启用时处理相机显示
|
|
|
|
|
|
if self.camera_enabled and hasattr(self, 'camera_display'):
|
|
|
|
|
|
# 停止显示相机画面
|
|
|
|
|
|
self.camera_display.stop_display()
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
QMessageBox.information(self, "操作提示", "生产线已暂停")
|
|
|
|
|
|
# 这里添加暂停生产线的业务逻辑
|
|
|
|
|
|
|
|
|
|
|
|
def handle_camera_status(self, is_connected, message):
|
|
|
|
|
|
"""处理相机状态变化"""
|
|
|
|
|
|
if is_connected:
|
|
|
|
|
|
logging.info("相机已连接并显示")
|
|
|
|
|
|
else:
|
|
|
|
|
|
logging.warning(f"相机显示问题: {message}")
|
|
|
|
|
|
|
|
|
|
|
|
def handle_camera_connection(self, is_connected, message):
|
|
|
|
|
|
"""处理相机连接状态变化"""
|
|
|
|
|
|
if is_connected:
|
|
|
|
|
|
logging.info("相机已连接")
|
|
|
|
|
|
# 如果当前在主页面,直接开始显示相机画面
|
|
|
|
|
|
if self.stacked_widget.currentWidget() == self.central_widget:
|
|
|
|
|
|
self.camera_display.start_display()
|
|
|
|
|
|
else:
|
|
|
|
|
|
if message:
|
|
|
|
|
|
logging.warning(f"相机连接失败: {message}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
logging.info("相机已断开")
|
|
|
|
|
|
# 如果相机断开,确保停止显示
|
|
|
|
|
|
self.camera_display.stop_display()
|
|
|
|
|
|
|
|
|
|
|
|
def handle_camera_params_changed(self, exposure_time, gain, frame_rate):
|
|
|
|
|
|
"""处理相机参数变化"""
|
|
|
|
|
|
logging.info(f"相机参数已更新: 曝光={exposure_time:.1f}μs, 增益={gain:.1f}dB, 帧率={frame_rate:.1f}fps")
|
|
|
|
|
|
# 这里可以添加对相机参数变化的处理逻辑
|
|
|
|
|
|
|
|
|
|
|
|
def handle_camera_error(self, error_msg):
|
|
|
|
|
|
"""处理相机错误"""
|
|
|
|
|
|
logging.error(f"相机错误: {error_msg}")
|
|
|
|
|
|
QMessageBox.warning(self, "相机错误", error_msg)
|
|
|
|
|
|
|
|
|
|
|
|
def closeEvent(self, event):
|
|
|
|
|
|
"""窗口关闭事件"""
|
2025-06-07 14:10:32 +08:00
|
|
|
|
# 只有在相机启用时处理相机关闭
|
|
|
|
|
|
if self.camera_enabled and hasattr(self, 'camera_display'):
|
|
|
|
|
|
# 停止相机显示
|
|
|
|
|
|
self.camera_display.stop_display()
|
2025-06-07 10:45:09 +08:00
|
|
|
|
|
|
|
|
|
|
# 接受关闭事件
|
2025-06-07 16:44:27 +08:00
|
|
|
|
event.accept()
|
|
|
|
|
|
|
|
|
|
|
|
def handle_order_enter(self):
|
|
|
|
|
|
"""处理工程号输入框按下回车事件"""
|
|
|
|
|
|
logging.info("工程号输入框按下回车事件")
|
|
|
|
|
|
# 获取当前输入的工程号
|
|
|
|
|
|
order_text = self.order_edit.text().strip()
|
|
|
|
|
|
|
|
|
|
|
|
if order_text:
|
|
|
|
|
|
logging.info(f"输入的工程号: {order_text}")
|
2025-06-08 01:24:27 +08:00
|
|
|
|
|
|
|
|
|
|
# 在微丝产线表格中添加一条新记录
|
|
|
|
|
|
self.add_new_inspection_row(order_text)
|
|
|
|
|
|
|
2025-06-07 16:44:27 +08:00
|
|
|
|
else:
|
|
|
|
|
|
logging.warning("工程号为空")
|
|
|
|
|
|
QMessageBox.warning(self, "输入提示", "请输入有效的工程号")
|
|
|
|
|
|
|
|
|
|
|
|
# 处理完后可以清除焦点,让输入框失去焦点
|
2025-06-08 01:24:27 +08:00
|
|
|
|
self.central_widget.setFocus()
|
|
|
|
|
|
|
|
|
|
|
|
def add_new_inspection_row(self, order_id):
|
|
|
|
|
|
"""在微丝产线表格中添加一条新记录
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
order_id: 工程号
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 获取启用的检验配置
|
|
|
|
|
|
enabled_configs = self.inspection_manager.get_enabled_configs()
|
|
|
|
|
|
|
|
|
|
|
|
# 固定的数据起始行索引
|
|
|
|
|
|
data_start_row = 2 # 数据从第3行开始
|
|
|
|
|
|
|
|
|
|
|
|
# 在指定行索引插入新行
|
|
|
|
|
|
self.process_table.insertRow(data_start_row)
|
|
|
|
|
|
|
|
|
|
|
|
# 更新序号 - 所有现有行序号+1
|
|
|
|
|
|
for row in range(data_start_row + 1, self.process_table.rowCount()):
|
|
|
|
|
|
seq_item = self.process_table.item(row, 0)
|
|
|
|
|
|
if seq_item:
|
|
|
|
|
|
try:
|
|
|
|
|
|
current_seq = int(seq_item.text())
|
|
|
|
|
|
seq_item.setText(str(current_seq + 1))
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
# 添加工程号到表格的第二列
|
|
|
|
|
|
item = QTableWidgetItem(order_id)
|
|
|
|
|
|
item.setTextAlignment(Qt.AlignCenter)
|
|
|
|
|
|
self.process_table.setItem(data_start_row, 1, item)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加序号到表格的第一列 - 新行始终是第1条
|
|
|
|
|
|
item = QTableWidgetItem("1")
|
|
|
|
|
|
item.setTextAlignment(Qt.AlignCenter)
|
|
|
|
|
|
self.process_table.setItem(data_start_row, 0, item)
|
|
|
|
|
|
|
|
|
|
|
|
# 检验列设置为可编辑状态
|
|
|
|
|
|
for i, config in enumerate(enabled_configs):
|
|
|
|
|
|
col_index = 2 + i # 检验列从第3列开始
|
|
|
|
|
|
|
|
|
|
|
|
# 创建空的可编辑单元格
|
|
|
|
|
|
item = QTableWidgetItem("")
|
|
|
|
|
|
item.setTextAlignment(Qt.AlignCenter)
|
|
|
|
|
|
# 设置单元格属性以标识其关联的检验项
|
|
|
|
|
|
item.setData(Qt.UserRole, config.get('id'))
|
|
|
|
|
|
self.process_table.setItem(data_start_row, col_index, item)
|
|
|
|
|
|
|
|
|
|
|
|
# 包装列设置为可编辑状态
|
|
|
|
|
|
packaging_start_col = 2 + len(enabled_configs)
|
|
|
|
|
|
for i in range(2): # 贴标和称重
|
|
|
|
|
|
col_index = packaging_start_col + i
|
|
|
|
|
|
item = QTableWidgetItem("")
|
|
|
|
|
|
item.setTextAlignment(Qt.AlignCenter)
|
|
|
|
|
|
self.process_table.setItem(data_start_row, col_index, item)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置表格为可编辑状态
|
|
|
|
|
|
self.process_table.setEditTriggers(QTableWidget.DoubleClicked | QTableWidget.EditKeyPressed)
|
|
|
|
|
|
|
|
|
|
|
|
# 连接单元格内容变更信号
|
|
|
|
|
|
# 断开之前的连接(如果有)
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.process_table.cellChanged.disconnect(self.handle_inspection_cell_changed)
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass # 如果没有连接过,会抛出异常,忽略即可
|
|
|
|
|
|
|
|
|
|
|
|
# 重新连接信号
|
|
|
|
|
|
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
|
|
|
|
|
|
|
|
|
|
|
|
# 选中新添加的行
|
|
|
|
|
|
self.process_table.selectRow(data_start_row)
|
|
|
|
|
|
|
|
|
|
|
|
# 限制最大行数
|
|
|
|
|
|
self.limit_table_rows(10) # 最多保留10行数据
|
|
|
|
|
|
|
|
|
|
|
|
logging.info(f"已添加工程号 {order_id} 的新记录,显示在第1条")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"添加新记录失败: {str(e)}")
|
|
|
|
|
|
QMessageBox.warning(self, "添加失败", f"添加新记录失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
def limit_table_rows(self, max_rows):
|
|
|
|
|
|
"""限制表格最大行数
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
max_rows: 最大行数(不包括表头行)
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 计算数据总行数
|
|
|
|
|
|
data_rows = self.process_table.rowCount() - 2 # 减去表头行
|
|
|
|
|
|
|
|
|
|
|
|
# 如果超过最大行数,删除多余的行
|
|
|
|
|
|
if data_rows > max_rows:
|
|
|
|
|
|
# 要删除的行数
|
|
|
|
|
|
rows_to_remove = data_rows - max_rows
|
|
|
|
|
|
|
|
|
|
|
|
# 从最后一行开始删除
|
|
|
|
|
|
for i in range(rows_to_remove):
|
|
|
|
|
|
self.process_table.removeRow(self.process_table.rowCount() - 1)
|
|
|
|
|
|
|
|
|
|
|
|
logging.info(f"已限制表格最大行数为 {max_rows} 行数据,删除了 {rows_to_remove} 行")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"限制表格行数失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
def handle_inspection_cell_changed(self, row, column):
|
|
|
|
|
|
"""处理检验单元格内容变更
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
row: 行索引
|
|
|
|
|
|
column: 列索引
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 只处理数据行的检验列变更
|
|
|
|
|
|
if row < 2: # 忽略表头行
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 忽略首尾两列(序号和工程号)
|
|
|
|
|
|
if column < 2:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 获取工程号
|
|
|
|
|
|
order_id_item = self.process_table.item(row, 1)
|
|
|
|
|
|
if not order_id_item:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
order_id = order_id_item.text().strip()
|
|
|
|
|
|
if not order_id:
|
|
|
|
|
|
return
|
2025-06-09 01:16:49 +08:00
|
|
|
|
|
|
|
|
|
|
# 获取托盘号
|
|
|
|
|
|
tray_id = self.tray_edit.currentText()
|
2025-06-08 01:24:27 +08:00
|
|
|
|
|
|
|
|
|
|
# 获取启用的检验配置
|
|
|
|
|
|
enabled_configs = self.inspection_manager.get_enabled_configs()
|
|
|
|
|
|
|
|
|
|
|
|
# 判断是否是检验列(非包装列)
|
|
|
|
|
|
packaging_start_col = 2 + len(enabled_configs)
|
|
|
|
|
|
if column >= 2 and column < packaging_start_col:
|
|
|
|
|
|
# 是检验列
|
|
|
|
|
|
config_index = column - 2
|
|
|
|
|
|
if config_index < len(enabled_configs):
|
|
|
|
|
|
config = enabled_configs[config_index]
|
|
|
|
|
|
|
|
|
|
|
|
# 获取单元格内容
|
|
|
|
|
|
cell_item = self.process_table.item(row, column)
|
|
|
|
|
|
if not cell_item:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
value = cell_item.text().strip()
|
|
|
|
|
|
|
|
|
|
|
|
# 显示临时状态消息
|
|
|
|
|
|
self.statusBar().showMessage(f"正在保存检验数据: {config['display_name']}={value}", 1000)
|
|
|
|
|
|
|
|
|
|
|
|
# 验证数据有效性
|
|
|
|
|
|
if self.validate_inspection_value(config, value):
|
|
|
|
|
|
# 设置单元格颜色为通过
|
|
|
|
|
|
cell_item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色
|
|
|
|
|
|
status = 'pass'
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 设置单元格颜色为警告
|
|
|
|
|
|
cell_item.setBackground(QBrush(QColor("#fff9c4"))) # 浅黄色
|
|
|
|
|
|
status = 'warning'
|
|
|
|
|
|
|
|
|
|
|
|
# 保存到数据库
|
2025-06-09 01:16:49 +08:00
|
|
|
|
self.save_inspection_data(order_id, tray_id, config['position'], config['id'], value, status)
|
|
|
|
|
|
|
|
|
|
|
|
# 触发查询, 更新页面记录回显
|
|
|
|
|
|
self.load_unfinished_inspection_data()
|
2025-06-08 01:24:27 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"处理检验单元格变更失败: {str(e)}")
|
|
|
|
|
|
self.statusBar().showMessage(f"处理检验数据失败: {str(e)[:50]}...", 3000)
|
|
|
|
|
|
|
|
|
|
|
|
def validate_inspection_value(self, config, value):
|
|
|
|
|
|
"""验证检验值是否有效
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
config: 检验配置
|
|
|
|
|
|
value: 检验值
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
bool: 是否有效
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 检查值是否为空
|
|
|
|
|
|
if not value and config.get('required', False):
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
# 根据数据类型验证
|
|
|
|
|
|
data_type = config.get('data_type')
|
|
|
|
|
|
|
|
|
|
|
|
if data_type == 'number':
|
|
|
|
|
|
# 数值类型验证
|
|
|
|
|
|
try:
|
|
|
|
|
|
num_value = float(value)
|
|
|
|
|
|
min_value = config.get('min_value')
|
|
|
|
|
|
max_value = config.get('max_value')
|
|
|
|
|
|
|
|
|
|
|
|
if min_value is not None and num_value < min_value:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
if max_value is not None and num_value > max_value:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
elif data_type == 'enum':
|
|
|
|
|
|
# 枚举类型验证
|
|
|
|
|
|
enum_values = config.get('enum_values')
|
|
|
|
|
|
if enum_values and isinstance(enum_values, list):
|
|
|
|
|
|
return value in enum_values
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
# 文本类型不做特殊验证
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"验证检验值失败: {str(e)}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
2025-06-09 01:16:49 +08:00
|
|
|
|
def save_inspection_data(self, order_id, tray_id, position, config_id, value, status):
|
2025-06-08 01:24:27 +08:00
|
|
|
|
"""保存检验数据到数据库
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
order_id: 工程号
|
|
|
|
|
|
position: 位置序号
|
|
|
|
|
|
config_id: 配置ID
|
|
|
|
|
|
value: 检验值
|
|
|
|
|
|
status: 状态
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
from dao.inspection_dao import InspectionDAO
|
|
|
|
|
|
inspection_dao = InspectionDAO()
|
|
|
|
|
|
|
|
|
|
|
|
# 记录保存前的详细日志
|
2025-06-09 01:16:49 +08:00
|
|
|
|
logging.info(f"正在保存检验数据: 工程号={order_id}, 托盘号={tray_id}, 位置={position}, 配置ID={config_id}, 值={value}, 状态={status}")
|
2025-06-08 01:24:27 +08:00
|
|
|
|
|
|
|
|
|
|
# 构建数据
|
|
|
|
|
|
data = [{
|
|
|
|
|
|
'position': position,
|
|
|
|
|
|
'config_id': config_id,
|
|
|
|
|
|
'value': value,
|
|
|
|
|
|
'status': status,
|
2025-06-09 01:16:49 +08:00
|
|
|
|
'remark': '',
|
|
|
|
|
|
'tray_id': tray_id
|
2025-06-08 01:24:27 +08:00
|
|
|
|
}]
|
|
|
|
|
|
|
|
|
|
|
|
# 保存到数据库
|
|
|
|
|
|
result = inspection_dao.save_inspection_data(order_id, data)
|
2025-06-09 01:16:49 +08:00
|
|
|
|
# 判断,如果保存成功后,且当前工程号中,全部为 pass 说明该工序已经完成,需要流转到下一步,回显到包装记录中
|
2025-06-08 01:24:27 +08:00
|
|
|
|
if result:
|
|
|
|
|
|
logging.info(f"已成功保存工程号 {order_id} 的检验数据,位置: {position}, 值: {value}")
|
|
|
|
|
|
# 显示临时状态消息
|
|
|
|
|
|
self.statusBar().showMessage(f"已保存检验数据:{value}", 3000)
|
|
|
|
|
|
else:
|
|
|
|
|
|
logging.warning(f"保存工程号 {order_id} 的检验数据失败")
|
|
|
|
|
|
# 显示错误消息
|
|
|
|
|
|
self.statusBar().showMessage(f"保存检验数据失败", 3000)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"保存检验数据失败: {str(e)}")
|
|
|
|
|
|
# 显示错误消息
|
|
|
|
|
|
self.statusBar().showMessage(f"保存检验数据错误: {str(e)[:50]}...", 3000)
|
|
|
|
|
|
|
|
|
|
|
|
def show_table_context_menu(self, pos):
|
|
|
|
|
|
"""显示表格上下文菜单
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
pos: 鼠标位置
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 获取当前单元格
|
|
|
|
|
|
cell_index = self.process_table.indexAt(pos)
|
|
|
|
|
|
if not cell_index.isValid():
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
row = cell_index.row()
|
|
|
|
|
|
column = cell_index.column()
|
|
|
|
|
|
|
|
|
|
|
|
# 只对数据行和检验列显示上下文菜单
|
|
|
|
|
|
if row < 2: # 忽略表头行
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 获取工程号
|
|
|
|
|
|
order_id_item = self.process_table.item(row, 1)
|
|
|
|
|
|
if not order_id_item:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
order_id = order_id_item.text().strip()
|
|
|
|
|
|
if not order_id:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
2025-06-09 01:16:49 +08:00
|
|
|
|
# 获取托盘号
|
|
|
|
|
|
tray_id = self.tray_edit.currentText()
|
|
|
|
|
|
|
2025-06-08 01:24:27 +08:00
|
|
|
|
# 创建上下文菜单
|
|
|
|
|
|
menu = QMenu(self)
|
|
|
|
|
|
|
|
|
|
|
|
# 获取启用的检验配置
|
|
|
|
|
|
enabled_configs = self.inspection_manager.get_enabled_configs()
|
|
|
|
|
|
|
|
|
|
|
|
# 判断是否是检验列(非包装列)
|
|
|
|
|
|
packaging_start_col = 2 + len(enabled_configs)
|
|
|
|
|
|
if column >= 2 and column < packaging_start_col:
|
|
|
|
|
|
# 是检验列
|
|
|
|
|
|
config_index = column - 2
|
|
|
|
|
|
if config_index < len(enabled_configs):
|
|
|
|
|
|
config = enabled_configs[config_index]
|
|
|
|
|
|
position = config.get('position')
|
|
|
|
|
|
|
|
|
|
|
|
# 添加查询数据库菜单项
|
|
|
|
|
|
check_action = menu.addAction("检查数据库记录")
|
2025-06-09 01:16:49 +08:00
|
|
|
|
check_action.triggered.connect(lambda: self.check_database_record(order_id, position, tray_id))
|
2025-06-08 01:24:27 +08:00
|
|
|
|
|
|
|
|
|
|
# 显示菜单
|
|
|
|
|
|
menu.exec_(self.process_table.viewport().mapToGlobal(pos))
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"显示表格上下文菜单失败: {str(e)}")
|
|
|
|
|
|
|
2025-06-09 01:16:49 +08:00
|
|
|
|
def check_database_record(self, order_id, position, tray_id):
|
2025-06-08 01:24:27 +08:00
|
|
|
|
"""检查数据库记录
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
order_id: 工程号
|
|
|
|
|
|
position: 位置序号
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
from dao.inspection_dao import InspectionDAO
|
|
|
|
|
|
inspection_dao = InspectionDAO()
|
|
|
|
|
|
|
|
|
|
|
|
# 获取检验数据
|
2025-06-09 01:16:49 +08:00
|
|
|
|
inspection_data = inspection_dao.get_inspection_data_by_order(order_id, tray_id)
|
2025-06-08 01:24:27 +08:00
|
|
|
|
|
|
|
|
|
|
# 查找对应位置的数据
|
|
|
|
|
|
matching_data = None
|
|
|
|
|
|
for data in inspection_data:
|
|
|
|
|
|
if data.get('position') == position:
|
|
|
|
|
|
matching_data = data
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
# 显示结果
|
|
|
|
|
|
if matching_data:
|
|
|
|
|
|
value = matching_data.get('value')
|
|
|
|
|
|
status = matching_data.get('status')
|
|
|
|
|
|
|
|
|
|
|
|
message = f"数据库记录:\n\n"
|
|
|
|
|
|
message += f"工程号: {order_id}\n"
|
|
|
|
|
|
message += f"位置: {position}\n"
|
|
|
|
|
|
message += f"值: {value}\n"
|
|
|
|
|
|
message += f"状态: {status}\n"
|
|
|
|
|
|
|
|
|
|
|
|
QMessageBox.information(self, "数据库记录", message)
|
|
|
|
|
|
else:
|
|
|
|
|
|
QMessageBox.warning(self, "数据库记录", f"未找到工程号 {order_id} 位置 {position} 的数据")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"检查数据库记录失败: {str(e)}")
|
|
|
|
|
|
QMessageBox.warning(self, "查询失败", f"检查数据库记录失败: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
def load_unfinished_inspection_data(self):
|
|
|
|
|
|
"""加载未完成的检验数据并显示在表格中"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 使用InspectionDAO获取未完成的检验数据
|
|
|
|
|
|
from dao.inspection_dao import InspectionDAO
|
|
|
|
|
|
inspection_dao = InspectionDAO()
|
2025-06-09 01:16:49 +08:00
|
|
|
|
|
|
|
|
|
|
# 获取托盘号
|
|
|
|
|
|
tray_id = self.tray_edit.currentText()
|
|
|
|
|
|
|
2025-06-08 01:24:27 +08:00
|
|
|
|
# 使用get_inspection_data_unfinished获取未完成的数据
|
2025-06-09 01:16:49 +08:00
|
|
|
|
unfinished_data = inspection_dao.get_inspection_data_unfinished(tray_id)
|
2025-06-08 01:24:27 +08:00
|
|
|
|
|
|
|
|
|
|
if not unfinished_data:
|
|
|
|
|
|
logging.info("没有未完成的检验数据")
|
2025-06-09 01:16:49 +08:00
|
|
|
|
# 清空表格现有数据行
|
|
|
|
|
|
while self.process_table.rowCount() > 2:
|
|
|
|
|
|
self.process_table.removeRow(2)
|
2025-06-08 01:24:27 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
logging.info(f"已加载未完成的检验数据,共 {len(unfinished_data)} 条记录")
|
2025-06-09 01:16:49 +08:00
|
|
|
|
|
2025-06-08 01:24:27 +08:00
|
|
|
|
# 获取启用的检验配置
|
|
|
|
|
|
enabled_configs = self.inspection_manager.get_enabled_configs()
|
|
|
|
|
|
|
|
|
|
|
|
# 按工程号分组
|
|
|
|
|
|
orders_data = {}
|
|
|
|
|
|
for data in unfinished_data:
|
|
|
|
|
|
order_id = data['order_id']
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加数据到表格
|
|
|
|
|
|
row_idx = 2 # 从第3行开始添加数据
|
|
|
|
|
|
for order_id, items in orders_data.items():
|
|
|
|
|
|
# 添加新行
|
|
|
|
|
|
self.process_table.insertRow(row_idx)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加序号到第一列
|
|
|
|
|
|
seq_item = QTableWidgetItem(str(row_idx - 1))
|
|
|
|
|
|
seq_item.setTextAlignment(Qt.AlignCenter)
|
|
|
|
|
|
self.process_table.setItem(row_idx, 0, seq_item)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加工程号到第二列
|
|
|
|
|
|
order_item = QTableWidgetItem(order_id)
|
|
|
|
|
|
order_item.setTextAlignment(Qt.AlignCenter)
|
|
|
|
|
|
self.process_table.setItem(row_idx, 1, order_item)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加检验数据
|
|
|
|
|
|
for item in items:
|
|
|
|
|
|
position = item['position']
|
|
|
|
|
|
value = item['value'] if item['value'] else ""
|
|
|
|
|
|
status = item['status']
|
|
|
|
|
|
config_id = item['config_id']
|
|
|
|
|
|
|
|
|
|
|
|
# 找到对应的列索引
|
|
|
|
|
|
col_index = None
|
|
|
|
|
|
for i, config in enumerate(enabled_configs):
|
|
|
|
|
|
if config.get('position') == position:
|
|
|
|
|
|
col_index = 2 + i # 检验列从第3列开始
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
if col_index is not None:
|
|
|
|
|
|
# 创建单元格并设置值
|
|
|
|
|
|
cell_item = QTableWidgetItem(str(value))
|
|
|
|
|
|
cell_item.setTextAlignment(Qt.AlignCenter)
|
|
|
|
|
|
# 存储配置ID,用于保存时确定是哪个检验项
|
|
|
|
|
|
cell_item.setData(Qt.UserRole, config_id)
|
|
|
|
|
|
|
|
|
|
|
|
# 根据状态设置单元格颜色
|
|
|
|
|
|
if status == 'fail':
|
|
|
|
|
|
cell_item.setBackground(QBrush(QColor("#ffcdd2"))) # 浅红色
|
|
|
|
|
|
elif status == 'warning':
|
|
|
|
|
|
cell_item.setBackground(QBrush(QColor("#fff9c4"))) # 浅黄色
|
|
|
|
|
|
elif status == 'pass':
|
|
|
|
|
|
cell_item.setBackground(QBrush(QColor("#c8e6c9"))) # 浅绿色
|
|
|
|
|
|
|
|
|
|
|
|
# 设置单元格
|
|
|
|
|
|
self.process_table.setItem(row_idx, col_index, cell_item)
|
|
|
|
|
|
|
|
|
|
|
|
row_idx += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 设置表格为可编辑状态
|
|
|
|
|
|
self.process_table.setEditTriggers(QTableWidget.DoubleClicked | QTableWidget.EditKeyPressed)
|
|
|
|
|
|
|
|
|
|
|
|
# 重新连接单元格变更信号
|
|
|
|
|
|
self.process_table.cellChanged.connect(self.handle_inspection_cell_changed)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logging.error(f"加载未完成的检验数据失败: {str(e)}")
|
|
|
|
|
|
QMessageBox.warning(self, "加载失败", f"加载未完成的检验数据失败: {str(e)}")
|