pyqt 简单条码系统

生产数据管理系统说明

在这里插入图片描述

系统概述

这是一个基于PyQt5和pyodbc开发的生产数据管理系统,主要用于管理生产过程中的物料绑定和查询操作。系统提供了上料绑定和下料查询功能,支持与SQL Server数据库交互,实现数据的插入、查询、更新和删除操作。界面采用标签页设计,操作流程清晰,反馈明确,适合生产环境使用。

主要功能模块

系统包含以下核心模块:

  1. 数据库管理模块(DatabaseManager)

    • 负责与SQL Server数据库建立连接和交互
    • 实现数据插入、查询、更新和删除操作
    • 提供表和列的存在性检查功能
    • 记录数据库操作日志
  2. 上料绑定模块(MaterialBindingWidget)

    • 常温上料绑定
    • 高温上料插管座绑定
    • 高温上料拔管座绑定
    • 支持扫码信息和批次信息录入
    • 数据查询和结果展示
    • 右键删除记录功能
  3. 下料查询模块(QueryWidget)

    • 扫码查询下料记录
    • 结果以表格形式展示
    • 按时间由近到远排序
    • 无结果时显示友好提示
  4. 日志管理模块(Logger)

    • 全局日志记录
    • 带时间戳的日志消息
    • 日志自动清理功能
    • 日志显示在主界面底部
界面设计特点
  1. 标签页布局

    • 四个主要功能标签页:常温上料绑定、高温上料插管座绑定、高温上料拔管座绑定、下料查询
    • 界面整洁,功能分区明确
  2. 按钮设计

    • 绿色提交按钮(#4CAF50)表示正向操作
    • 黄色按钮表示中间状态
    • 红色按钮表示错误状态
    • 按钮悬停和按下状态有视觉反馈
  3. 结果展示

    • 表格形式展示查询结果
    • 列宽自动适应内容
    • 支持右键删除记录
  4. 操作反馈

    • 提交操作后显示明确的状态提示
    • 操作过程中按钮状态变化
    • 日志区域实时记录操作信息
数据库操作流程
  1. 数据插入流程

    • 先更新原有数据状态
    • 再插入新数据
    • 提交事务
    • 记录操作日志
  2. 数据查询流程

    • 构建查询条件
    • 执行SQL查询
    • 按时间降序排列结果
    • 填充到表格中展示
  3. 数据删除流程

    • 右键选择记录
    • 确认删除操作
    • 执行删除SQL
    • 提交事务
    • 重新查询刷新结果
颜色说明
  1. 绿色(#4CAF50)

    • 用于表示成功状态
    • 提交按钮背景色
    • 成功提示文字颜色
  2. 黄色(#FFC107)

    • 用于表示执行中状态
    • 中间状态按钮背景色
  3. 红色(#F44336)

    • 用于表示错误状态
    • 失败提示文字和按钮颜色
  4. 绿色(#008000)

    • 用于标题文字
    • 表示正常状态
特殊功能说明
  1. 状态提示按钮

    • 提交操作时按钮状态变化:初始 -> 执行中 -> 成功/失败
    • 视觉反馈清晰,操作状态一目了然
  2. 自动清空日志

    • 当日志行数超过10行且滚动条在底部时自动清空
    • 保留最新的日志记录
  3. 扫码规则检查

    • 自动检查扫码信息长度
    • 长度小于10时提示不符合规则
  4. 日期范围查询

    • 默认为最近7天的数据
    • 支持自定义日期范围查询
使用说明
  1. 数据库配置

    • 在MainWindow类中修改db_config字典中的服务器地址、数据库名称、用户名和密码
  2. 上料绑定

    • 输入扫码信息和批次信息
    • 点击"提交"按钮绑定数据
    • 按钮状态显示操作结果
    • 可在查询区域查询历史记录
  3. 下料查询

    • 输入扫码信息
    • 点击"查询"按钮
    • 结果显示在表格中,按时间由近到远排列
  4. 日志查看

    • 主界面底部显示系统操作日志
    • 包含时间戳和操作详情
技术要点
  1. 数据库连接管理

    • 使用pyodbc连接SQL Server
    • 自动重连机制
    • 事务管理确保数据一致性
  2. 界面交互

    • 使用信号与槽机制
    • 表格组件的上下文菜单
    • 输入框回车提交功能
  3. 代码结构

    • 面向对象设计,模块职责明确
    • 日志系统独立封装
    • 界面组件继承复用

这个生产数据管理系统适合在生产环境中使用,提供了完整的物料绑定和查询功能,界面友好,操作简单,同时具备完善的日志记录和错误处理机制。

import sys
import traceback
import pyodbc
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout,
                             QHBoxLayout, QLabel, QLineEdit, QPushButton, QTextEdit,
                             QMessageBox, QGroupBox, QTableWidget, QTableWidgetItem,
                             QHeaderView, QMenu, QAction, QInputDialog, QDateEdit)
from PyQt5.QtCore import Qt, pyqtSignal, QObject, QDate, QDateTime
from PyQt5.QtGui import QFont, QColor, QPalette
import datetime


class Logger(QObject):
    """全局日志管理器,通过信号传递日志消息"""
    log_message = pyqtSignal(str)
    
    def log(self, message):
        """发送带时间戳的日志消息到信号"""
        timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]  # 生成时间戳,精确到毫秒
        timestamped_message = f"[{timestamp}] {message}"
        self.log_message.emit(timestamped_message)


class LogSignal(QObject):
    """数据库操作日志信号类"""
    log_message = pyqtSignal(str)


class DatabaseManager:
    """数据库管理类,负责与SQL Server交互"""

    def __init__(self, server, database, username, password, logger=None):
        self.server = server
        self.database = database
        self.username = username
        self.password = password
        self.connection = None
        self.logger = logger
        self.db_log_signal = LogSignal()

    def get_connection_string(self):
        """获取数据库连接字符串"""
        return (
            f'DRIVER={{ODBC Driver 17 for SQL Server}};'
            f'SERVER={self.server};'
            f'DATABASE={self.database};'
            f'UID={self.username};'
            f'PWD={self.password}'
        )

    def connect(self):
        """连接到SQL Server数据库"""
        try:
            connection_string = self.get_connection_string()
            self.log_message(f"[数据库] 尝试连接: {connection_string}")
            self.connection = pyodbc.connect(connection_string)
            self.log_message("[数据库] 连接成功")
            return True
        except Exception as e:
            error_msg = f"数据库连接错误: {str(e)}"
            self.log_message(f"[错误] {error_msg}")
            return False

    def disconnect(self):
        """断开数据库连接"""
        if self.connection:
            try:
                self.connection.close()
                self.log_message("[数据库] 连接已断开")
            except Exception as e:
                self.log_message(f"[错误] 断开数据库连接时出错: {str(e)}")
            finally:
                self.connection = None

    def execute_query(self, query, params=None):
        """执行SQL查询"""
        if not self.connection:
            if not self.connect():
                raise Exception("无法建立数据库连接")

        try:
            cursor = self.connection.cursor()
            query_str = query
            if params:
                query_str = f"{query} (参数: {params})"
            self.log_message(f"[SQL] 执行查询: {query_str}")
            cursor.execute(query, params)
            return cursor
        except Exception as e:
            error_msg = f"SQL执行错误: {str(e)}"
            self.log_message(f"[错误] {error_msg}")
            raise

    def commit(self):
        """提交事务"""
        if self.connection:
            try:
                self.connection.commit()
                self.log_message("[数据库] 事务已提交")
            except Exception as e:
                error_msg = f"提交事务时出错: {str(e)}"
                self.log_message(f"[错误] {error_msg}")
                raise

    def update_data(self, table_name, barcode):
        """更新指定条码的状态为1"""
        query = f"UPDATE {table_name} SET state = 1 WHERE barcode = ?"
        try:
            cursor = self.execute_query(query, (barcode,))
            if cursor:
                affected_rows = cursor.rowcount  # 获取受影响的行数
                self.commit()
                self.log_message(f"[成功] 已更新 {table_name} 表中 {affected_rows} 条记录的状态")
                return True
            return False
        except Exception as e:
            error_msg = f"更新数据时出错: {str(e)}"
            self.log_message(f"[错误] {error_msg}")
            raise

    def insert_data(self, table_name, barcode, batch):
        """插入数据到指定表,插入前先更新原有数据状态"""
        try:
            # 先更新原有数据状态
            self.update_data(table_name, barcode)
            # 再插入新数据
            query = f"INSERT INTO {table_name} (barcode, batch, STATE) VALUES (?, ?, 0)"
            cursor = self.execute_query(query, (barcode, batch))
            if cursor:
                self.commit()
                self.log_message(f"[成功] 数据已成功插入到 {table_name} 表")
                return True
            return False
        except Exception as e:
            error_msg = f"插入数据时出错: {str(e)}"
            self.log_message(f"[错误] {error_msg}")
            raise

    def query_data(self, table_name, barcode=None, start_date=None, end_date=None):
        """从指定表查询数据,结果按时间由近到远排序"""
        params = []
        query = f"SELECT barcode, batch, TIME FROM {table_name} WHERE 1=1"

        if barcode:
            query += " AND barcode = ?"
            params.append(barcode)

        if start_date:
            query += " AND TIME >= ?"
            params.append(start_date)

        if end_date:
            query += " AND TIME <= ?"
            params.append(end_date)

        query += " ORDER BY TIME DESC"  # 按时间降序排列,最新时间在前

        try:
            cursor = self.execute_query(query, params)
            if cursor:
                results = []
                for row_num, row in enumerate(cursor.fetchall(), 1):  # 从1开始编号
                    results.append({
                        'id': row_num,  # 本地自增ID,用于显示序号
                        'barcode': row.barcode,
                        'batch': row.batch,
                        'TIME': row.TIME
                    })
                return results
            return []
        except Exception as e:
            error_msg = f"查询数据时出错: {str(e)}"
            self.log_message(f"[错误] {error_msg}")
            raise

    def delete_data(self, table_name, barcode, time):
        """从指定表删除数据,使用barcode和TIME作为唯一标识"""
        query = f"DELETE FROM {table_name} WHERE barcode = ? AND TIME = ?"
        try:
            cursor = self.execute_query(query, (barcode, time))
            if cursor:
                self.commit()
                self.log_message(f"[成功] 已从 {table_name} 表删除条码为 '{barcode}' 时间为 '{time}' 的记录")
                return True
            return False
        except Exception as e:
            error_msg = f"删除数据时出错: {str(e)}"
            self.log_message(f"[错误] {error_msg}")
            raise

    def check_table_exists(self, table_name):
        """检查指定表是否存在"""
        try:
            query = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ?"
            cursor = self.execute_query(query, (table_name,))
            result = cursor.fetchone()
            return result[0] > 0
        except Exception as e:
            error_msg = f"检查表格存在性时出错: {str(e)}"
            self.log_message(f"[错误] {error_msg}")
            return False

    def check_column_exists(self, table_name, column_name):
        """检查指定列是否存在于表中"""
        try:
            query = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ? AND COLUMN_NAME = ?"
            cursor = self.execute_query(query, (table_name, column_name))
            result = cursor.fetchone()
            return result[0] > 0
        except Exception as e:
            error_msg = f"检查列存在性时出错: {str(e)}"
            self.log_message(f"[错误] {error_msg}")
            return False

    def log_message(self, message):
        """记录日志消息(优先使用全局日志管理器)"""
        if self.logger:
            self.logger.log(message)
        if self.db_log_signal:
            self.db_log_signal.log_message.emit(message)


class MaterialBindingWidget(QWidget):
    """上料绑定模块基类"""

    def __init__(self, db_manager, logger=None, parent=None, table_name="T_IN", title="常温上料绑定"):
        super().__init__(parent)
        self.db_manager = db_manager
        self.table_name = table_name
        self.title = title
        self.logger = logger
        self.init_ui()

    def init_ui(self):
        # 创建主布局
        main_layout = QVBoxLayout(self)

        # 创建标题
        title_label = QLabel(self.title)
        title_font = QFont("SimHei", 28, QFont.Bold)
        title_label.setFont(title_font)
        title_label.setAlignment(Qt.AlignCenter)

        # 设置标题颜色为绿色
        palette = QPalette()
        palette.setColor(QPalette.WindowText, QColor(0, 128, 0))
        title_label.setPalette(palette)

        main_layout.addWidget(title_label)

        # 创建输入区域
        input_group = QGroupBox("数据录入")
        input_layout = QVBoxLayout(input_group)

        # 扫码信息
        barcode_layout = QHBoxLayout()
        barcode_label = QLabel("扫码信息:")
        barcode_label.setFixedWidth(160)
        barcode_font = QFont("SimHei", 20)
        barcode_label.setFont(barcode_font)

        self.barcode_edit = QLineEdit()
        self.barcode_edit.setPlaceholderText("请输入扫码信息")
        self.barcode_edit.setFont(barcode_font)
        # 连接回车键信号到提交方法
        self.barcode_edit.returnPressed.connect(self.on_submit)

        barcode_layout.addWidget(barcode_label)
        barcode_layout.addWidget(self.barcode_edit)
        input_layout.addLayout(barcode_layout)

        # 批次信息
        batch_layout = QHBoxLayout()
        batch_label = QLabel("批次信息:")
        batch_label.setFixedWidth(160)
        batch_label.setFont(barcode_font)

        self.batch_edit = QLineEdit()
        self.batch_edit.setPlaceholderText("请输入批次信息")
        self.batch_edit.setFont(barcode_font)

        batch_layout.addWidget(batch_label)
        batch_layout.addWidget(self.batch_edit)
        input_layout.addLayout(batch_layout)

        # 按钮区域
        button_layout = QHBoxLayout()
        self.submit_button = QPushButton("提交")
        self.clear_button = QPushButton("清空")
        self.result_button = QPushButton("结果显示")

        button_font = QFont("SimHei", 20)
        self.submit_button.setFont(button_font)
        self.clear_button.setFont(button_font)
        self.result_button.setFont(button_font)


        # 添加绿色按钮样式
        button_style = """
            QPushButton {
                background-color: #4CAF50;
                color: white;
                border-radius: 5px;
                padding: 8px;
            }
            QPushButton:hover {
                background-color: #45a049;
            }
            QPushButton:pressed {
                background-color: #3e8e41;
            }
        """
        button_styleNormal = """
            QPushButton {
                background-color: #FFC107;
                color: white;
                border-radius: 5px;
                padding: 8px;
            }
            QPushButton:hover {
                background-color: #FFC107;
            }
            QPushButton:pressed {
                background-color: #3e8e41;
            }
        """
        self.submit_button.setStyleSheet(button_style)
        self.clear_button.setStyleSheet(button_style)
        self.result_button.setStyleSheet(button_styleNormal)
        

        button_layout.addWidget(self.submit_button)
        button_layout.addWidget(self.clear_button)
        button_layout.addWidget(self.result_button)
        input_layout.addLayout(button_layout)

        main_layout.addWidget(input_group)

        # 创建查询区域
        query_group = QGroupBox("数据查询")
        query_layout = QVBoxLayout(query_group)

        # 查询条件区域
        query_cond_layout = QHBoxLayout()

        # 条码查询
        query_barcode_label = QLabel("条码:")
        query_barcode_label.setFont(barcode_font)
        self.query_barcode_edit = QLineEdit()
        self.query_barcode_edit.setPlaceholderText("输入条码查询")
        self.query_barcode_edit.setFont(barcode_font)

        # 日期范围查询
        date_label = QLabel("日期范围:")
        date_label.setFont(barcode_font)
        self.start_date_edit = QDateEdit()
        self.start_date_edit.setCalendarPopup(True)
        self.start_date_edit.setDate(QDate.currentDate().addDays(-7))  # 默认显示一周内的数据
        self.start_date_edit.setFont(barcode_font)

        date_to_label = QLabel("至")
        date_to_label.setFont(barcode_font)

        self.end_date_edit = QDateEdit()
        self.end_date_edit.setCalendarPopup(True)
        self.end_date_edit.setDate(QDate.currentDate())
        self.end_date_edit.setFont(barcode_font)

        # 查询按钮(宽度加倍)
        self.query_button = QPushButton("查询")
        self.query_button.setFont(barcode_font)
        self.query_button.setStyleSheet(button_style)
        self.query_button.setMinimumWidth(200)  # 查询按钮宽度加倍

        query_cond_layout.addWidget(query_barcode_label)
        query_cond_layout.addWidget(self.query_barcode_edit)
        query_cond_layout.addWidget(date_label)
        query_cond_layout.addWidget(self.start_date_edit)
        query_cond_layout.addWidget(date_to_label)
        query_cond_layout.addWidget(self.end_date_edit)
        query_cond_layout.addWidget(self.query_button)

        query_layout.addLayout(query_cond_layout)

        # 查询结果表格
        self.result_table = QTableWidget()
        self.result_table.setColumnCount(4)
        self.result_table.setHorizontalHeaderLabels(["序号", "条码", "批次", "时间"])
        self.result_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.result_table.setContextMenuPolicy(Qt.CustomContextMenu)
        self.result_table.customContextMenuRequested.connect(self.show_context_menu)

        query_layout.addWidget(self.result_table)

        main_layout.addWidget(query_group)

        # 连接信号和槽
        self.submit_button.clicked.connect(self.on_submit)
        self.clear_button.clicked.connect(self.on_clear)
        self.query_button.clicked.connect(self.on_query)

    def log_message(self, message):
        """记录日志消息(使用全局日志管理器)"""
        try:
            if self.logger:
                self.logger.log(message)
            else:
                print(f"[未设置日志管理器] {message}")
        except Exception as e:
            print(f"日志记录失败: {str(e)}")
            print(message)

    def on_submit(self):
        button_stylePass = """
            QPushButton {
                background-color: #4CAF50;
                color: white;
                border-radius: 5px;
                padding: 8px;
            }
        """
        button_styleIng = """
            QPushButton {
                background-color: #FFC107;
                color: white;
                border-radius: 5px;
                padding: 8px;
            }
        """
        button_styleErr = """
            QPushButton {
                background-color: #F44336;
                color: white;
                border-radius: 5px;
                padding: 8px;
            }
        """   
        self.result_button.setText("执行中")
        self.result_button.setStyleSheet(button_styleIng)
        """提交数据到数据库"""
        barcode = self.barcode_edit.text().strip()
        batch = self.batch_edit.text().strip()
        
        # 开始判断条码是否符合编码规则
        if len(barcode) < 10:
            self.log_message(f"[警告] {barcode} 扫码不符合规则!")
            self.result_button.setText("FAIL")
            self.result_button.setStyleSheet(button_styleErr)
            return

        if not barcode or not batch:
            self.log_message("[警告] 扫码信息和批次信息不能为空!")
            return

        self.log_message(f"[操作] 点击提交按钮,条码: {barcode}, 批次: {batch}")
        
        try:
            success = self.db_manager.insert_data(self.table_name, barcode, batch)
            if success:
                self.log_message(f"[成功] 条码绑定批次成功 {batch}, {barcode}")
                self.on_clear()
                self.result_button.setText("PASS")
                self.result_button.setStyleSheet(button_stylePass)
                
            else:
                self.log_message(f"[失败] 数据上传失败,请检查数据库连接!")
                self.result_button.setText("FAIL")
                self.result_button.setStyleSheet(button_styleErr)
                self.on_clear()
        except Exception as e:
            self.result_button.setText("FAIL")
            self.result_button.setStyleSheet(button_styleErr)
            error_details = traceback.format_exc()
            error_msg = f"提交数据时发生错误: {str(e)}"
            QMessageBox.critical(self, "错误", error_msg)
            self.log_message(f"[错误] {error_msg}")
            self.log_message(f"[详细] {error_details}")
            self.on_clear()

    def on_clear(self):
        """清空输入框"""
        self.barcode_edit.clear()
        self.barcode_edit.setFocus()

    def on_query(self):
        """查询数据"""
        self.log_message("[操作] 点击查询按钮")
        barcode = self.query_barcode_edit.text().strip()
        start_date = self.start_date_edit.date().toString("yyyy-MM-dd")
        end_date = self.end_date_edit.date().toString("yyyy-MM-dd") + " 23:59:59"  # 包含当天全部时间

        try:
            results = self.db_manager.query_data(self.table_name, barcode, start_date, end_date)

            # 清空表格
            self.result_table.setRowCount(0)

            # 填充表格
            for row_num, row_data in enumerate(results):
                self.result_table.insertRow(row_num)
                self.result_table.setItem(row_num, 0, QTableWidgetItem(str(row_data['id'])))
                self.result_table.setItem(row_num, 1, QTableWidgetItem(row_data['barcode']))
                self.result_table.setItem(row_num, 2, QTableWidgetItem(row_data['batch']))
                # 将datetime对象转换为字符串,精确到毫秒三位
                time_str = row_data['TIME'].strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
                self.result_table.setItem(row_num, 3, QTableWidgetItem(time_str))

            # 显示查询结果数量
            result_count = len(results)
            self.log_message(f"[查询] 在 {self.table_name} 表中找到 {result_count} 条记录")

        except Exception as e:
            error_details = traceback.format_exc()
            error_msg = f"查询数据时发生错误: {str(e)}"
            QMessageBox.critical(self, "错误", error_msg)
            self.log_message(f"[错误] {error_msg}")
            self.log_message(f"[详细] {error_details}")

    def show_context_menu(self, position):
        """显示右键菜单"""
        selected_row = self.result_table.rowAt(position.y())
        if selected_row >= 0:
            context_menu = QMenu(self)
            delete_action = QAction("删除记录", self)
            context_menu.addAction(delete_action)

            action = context_menu.exec_(self.result_table.mapToGlobal(position))
            if action == delete_action:
                self.delete_selected_record(selected_row)

    def delete_selected_record(self, row):
        """删除选中的记录"""
        barcode = self.result_table.item(row, 1).text()
        record_time = self.result_table.item(row, 3).text()

        reply = QMessageBox.question(self, "确认删除",
                                     f"确定要删除条码为 '{barcode}' 的记录吗?",
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

        if reply == QMessageBox.Yes:
            self.log_message(f"[操作] 确认删除条码为 '{barcode}' 的记录")
            try:
                success = self.db_manager.delete_data(self.table_name, barcode, record_time)
                if success:
                    self.log_message(f"[成功] 已删除 {self.table_name} 表中条码为 '{barcode}' 的记录")
                    # 删除成功后重新查询
                    self.on_query()
                else:
                    self.log_message(f"[失败] 删除记录失败,请检查数据库连接!")
            except Exception as e:
                error_details = traceback.format_exc()
                error_msg = f"删除数据时发生错误: {str(e)}"
                QMessageBox.critical(self, "错误", error_msg)
                self.log_message(f"[错误] {error_msg}")
                self.log_message(f"[详细] {error_details}")


class QueryWidget(QWidget):
    """下料查询模块,结果显示在表格中"""

    def __init__(self, db_manager, logger=None, parent=None):
        super().__init__(parent)
        self.db_manager = db_manager
        self.logger = logger
        self.init_ui()

    def init_ui(self):
        # 创建主布局
        main_layout = QVBoxLayout(self)

        # 创建标题
        title_label = QLabel("下料查询")
        title_font = QFont("SimHei", 28, QFont.Bold)
        title_label.setFont(title_font)
        title_label.setAlignment(Qt.AlignCenter)

        # 设置标题颜色为绿色
        palette = QPalette()
        palette.setColor(QPalette.WindowText, QColor(0, 128, 0))
        title_label.setPalette(palette)

        main_layout.addWidget(title_label)

        # 创建查询区域
        query_group = QGroupBox("查询")
        query_layout = QHBoxLayout(query_group)

        # 扫码信息
        barcode_label = QLabel("扫码查询:")
        barcode_label.setFixedWidth(160)
        barcode_font = QFont("SimHei", 20)
        barcode_label.setFont(barcode_font)

        self.barcode_edit = QLineEdit()
        self.barcode_edit.setPlaceholderText("请输入扫码信息")
        self.barcode_edit.setFont(barcode_font)

        self.query_button = QPushButton("查询")
        self.query_button.setFont(barcode_font)

        # 添加绿色按钮样式
        button_style = """
            QPushButton {
                background-color: #4CAF50;
                color: white;
                border-radius: 5px;
                padding: 8px;
            }
            QPushButton:hover {
                background-color: #45a049;
            }
            QPushButton:pressed {
                background-color: #3e8e41;
            }
        """
        self.query_button.setStyleSheet(button_style)
        self.query_button.setMinimumWidth(200)  # 查询按钮宽度加倍

        query_layout.addWidget(barcode_label)
        query_layout.addWidget(self.barcode_edit)
        query_layout.addWidget(self.query_button)

        main_layout.addWidget(query_group)

        # 创建结果显示区域
        result_group = QGroupBox("查询结果")
        result_layout = QVBoxLayout(result_group)

        # 使用表格显示查询结果
        self.result_table = QTableWidget()
        self.result_table.setColumnCount(4)
        self.result_table.setHorizontalHeaderLabels(["序号", "条码", "批次", "时间"])
        self.result_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

        result_layout.addWidget(self.result_table)

        main_layout.addWidget(result_group)

        # 连接信号和槽
        self.query_button.clicked.connect(self.on_query)
        self.barcode_edit.returnPressed.connect(self.on_query)

    def log_message(self, message):
        """记录日志消息(使用全局日志管理器)"""
        try:
            if self.logger:
                self.logger.log(message)
            else:
                print(f"[未设置日志管理器] {message}")
        except Exception as e:
            print(f"日志记录失败: {str(e)}")
            print(message)

    def on_query(self):
        """查询数据并清空输入框,结果显示在表格中"""
        self.log_message("[操作] 点击查询按钮")
        barcode = self.barcode_edit.text().strip()

        if not barcode:
            QMessageBox.warning(self, "警告", "扫码信息不能为空!")
            return

        try:
            results = self.db_manager.query_data("T_OUT", barcode)  # 获取查询结果

            # 清空输入框
            self.barcode_edit.clear()

            # 清空表格
            self.result_table.setRowCount(0)

            if results:
                # 填充表格
                for row_num, row_data in enumerate(results):
                    self.result_table.insertRow(row_num)
                    self.result_table.setItem(row_num, 0, QTableWidgetItem(str(row_data['id'])))
                    self.result_table.setItem(row_num, 1, QTableWidgetItem(row_data['barcode']))
                    self.result_table.setItem(row_num, 2, QTableWidgetItem(row_data['batch']))
                    # 将datetime对象转换为字符串,精确到毫秒三位
                    time_str = row_data['TIME'].strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
                    self.result_table.setItem(row_num, 3, QTableWidgetItem(time_str))

                result_count = len(results)
                self.log_message(f"[查询] 在下料表中找到 {result_count} 条记录,按时间由近到远显示")
            else:
                self.result_table.setRowCount(1)
                self.result_table.setItem(0, 0, QTableWidgetItem("无结果"))
                self.result_table.setItem(0, 1, QTableWidgetItem(f"未找到条码为 '{barcode}' 的记录"))
                self.result_table.setSpan(0, 0, 1, 4)  # 合并单元格
                self.result_table.item(0, 0).setTextAlignment(Qt.AlignCenter)
                self.log_message(f"[查询] 未找到条码为 '{barcode}' 的下料记录")
        except Exception as e:
            error_details = traceback.format_exc()
            error_msg = f"查询数据时发生错误: {str(e)}"
            QMessageBox.critical(self, "错误", error_msg)
            self.log_message(f"[错误] {error_msg}")
            self.log_message(f"[详细] {error_details}")


class MainWindow(QMainWindow):
    """主窗口类"""

    def __init__(self):
        super().__init__()

        # 数据库配置
        self.db_config = {
            'server': 'LEGENDLI',  # 请根据实际情况修改服务器地址
            'database': 'testbase',
            'username': 'sa',
            'password': '1'
        }

        # 初始化全局日志管理器
        self.logger = Logger()

        # 初始化数据库管理器并传递日志管理器
        self.db_manager = DatabaseManager(
            self.db_config['server'],
            self.db_config['database'],
            self.db_config['username'],
            self.db_config['password'],
            logger=self.logger
        )

        # 连接日志信号到界面显示
        self.logger.log_message.connect(self.log_to_ui)

        # 检查数据库连接
        if not self.db_manager.connect():
            QMessageBox.critical(self, "数据库连接失败", "无法连接到SQL Server数据库,请检查配置!")

        self.init_ui()

    def init_ui(self):
        # 设置窗口标题和大小
        self.setWindowTitle("生产数据管理系统")
        self.setGeometry(100, 100, 1800, 900)

        # 创建中心部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        # 创建主布局
        main_layout = QVBoxLayout(central_widget)

        # 创建标签页控件
        self.tab_widget = QTabWidget()

        # 1. 常温上料绑定
        self.normal_material_tab = MaterialBindingWidget(
            self.db_manager,
            logger=self.logger,
            table_name="T_IN",
            title="常温上料绑定"
        )
        self.tab_widget.addTab(self.normal_material_tab, "常温上料绑定")

        # 2. 高温上料插管座绑定
        self.hight_temp_insert_tab = MaterialBindingWidget(
            self.db_manager,
            logger=self.logger,
            table_name="T_HIGHTIN",
            title="高温上料插管座绑定"
        )
        self.tab_widget.addTab(self.hight_temp_insert_tab, "高温上料插管座绑定")

        # 3. 高温上料拔管座绑定
        self.hight_temp_remove_tab = MaterialBindingWidget(
            self.db_manager,
            logger=self.logger,
            table_name="T_HIGHTOUT",
            title="高温上料拔管座绑定"
        )
        self.tab_widget.addTab(self.hight_temp_remove_tab, "高温上料拔管座绑定")

        # 4. 下料查询
        self.query_tab = QueryWidget(
            self.db_manager,
            logger=self.logger
        )
        self.tab_widget.addTab(self.query_tab, "下料查询")

        # 将标签页控件添加到主布局
        main_layout.addWidget(self.tab_widget)

        # 创建日志区域
        try:
            log_group = QGroupBox("系统日志")
            log_title_font = QFont("SimHei", 20)
            log_group.setFont(log_title_font)

            log_layout = QVBoxLayout(log_group)
            self.log_text = QTextEdit()
            self.log_text.setReadOnly(True)
            self.log_text.setPlaceholderText("系统日志将显示在这里...")
            log_font = QFont("SimHei", 15)
            self.log_text.setFont(log_font)

            log_layout.addWidget(self.log_text)
            main_layout.addWidget(log_group)

            # 初始化日志
            self.log_to_ui("===== 系统已启动 =====")
            self.log_to_ui(f"数据库配置: 服务器={self.db_config['server']}, 数据库={self.db_config['database']}")
            self.log_to_ui("[系统] 按钮样式已设置为绿色,操作日志功能已启用")
        except Exception as e:
            print(f"创建日志区域失败: {str(e)}")
            QMessageBox.warning(self, "警告", f"创建日志区域失败: {str(e)}")

        # 设置全局字体
        global_font = QFont("SimHei", 20)
        QApplication.setFont(global_font)
        
        # 设置标题栏样式(Windows系统有效)
        self.setStyleSheet("""
            QMainWindow::title {
                font-size: 36px;  /* 标题字体大小翻倍,从18px增加到36px */
                color: #008000;   /* 标题颜色设置为绿色 */
                font-weight: bold;
            }
        """)

    def log_to_ui(self, message):
        """将日志消息添加到日志区域,并实现自动清空功能"""
        try:
            if hasattr(self, 'log_text'):
                # 添加新日志
                self.log_text.append(message)
                
                # 检查滚动条是否在底部
                vertical_scrollbar = self.log_text.verticalScrollBar()
                is_at_bottom = vertical_scrollbar.value() == vertical_scrollbar.maximum()
                
                # 如果滚动条在底部且日志行数超过100行,清空日志
                if is_at_bottom and self.log_text.document().lineCount() > 10:
                    self.log_text.clear()
                    self.log_to_ui(message)  # 重新添加当前日志(包含时间戳)
                
                # 确保显示最新日志
                vertical_scrollbar.setValue(vertical_scrollbar.maximum())
        except Exception as e:
            print(f"记录日志失败: {str(e)}")
            print(message)

    def closeEvent(self, event):
        """关闭窗口时断开数据库连接"""
        try:
            self.db_manager.disconnect()
        except Exception as e:
            print(f"关闭数据库连接失败: {str(e)}")
        event.accept()


if __name__ == "__main__":
    # 创建应用实例
    app = QApplication(sys.argv)

    # 创建并显示主窗口
    try:
        window = MainWindow()
        window.show()
    except Exception as e:
        print(f"创建主窗口失败: {str(e)}")
        QMessageBox.critical(None, "严重错误", f"应用程序无法启动: {str(e)}")
        sys.exit(1)

    # 进入应用主循环
    sys.exit(app.exec_())
### 深度学习与PyQt结合的实现方法及案例 深度学习与PyQt的结合能够为用户提供直观、便捷的图形界面,同时利用深度学习技术完成复杂的任务。以下是实现方法及具体案例的详细介绍。 #### 1. 实现方法 深度学习模型通常使用Python中的框架(如TensorFlow、PyTorch等)进行训练和推理,而PyQt是一个用于创建图形用户界面(GUI)的工具包。将两者结合的关键在于以下几点: - **模型加载与推理**:在PyQt应用中加载预训练好的深度学习模型,并通过调用模型接口完成推理任务[^4]。 - **数据流管理**:设计数据管道以处理输入(如图像、音频等),并将结果展示在GUI界面上[^1]。 - **事件驱动机制**:利用PyQt的信号与槽机制,响应用户的交互操作,触发深度学习模型的运行[^5]。 - **多线程支持**:为了避免GUI界面卡顿,可以将耗时的深度学习推理任务放在独立的线程中执行。 #### 2. 典型案例 ##### 案例一:手写数字识别系统 该系统通过结合Python、PyQt和BP神经网络技术,实现了手写数字的识别功能。用户可以在PyQt提供的画布上绘制数字,系统会实时显示识别结果。此案例展示了深度学习在图像处理领域的应用前景以及PyQt在图形用户界面设计中的强大功能[^1]。 ```python # 手写数字识别系统的部分代码示例 from PyQt5.QtWidgets import QApplication, QWidget import sys class HandwritingRecognition(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): # 初始化UI组件 pass if __name__ == '__main__': app = QApplication(sys.argv) ex = HandwritingRecognition() sys.exit(app.exec_()) ``` ##### 案例二:六大深度学习应用案例 基于OpenVINO C++ SDK集成QT,开发了六个深度学习应用案例,包括人像抠图、背景提取与替换、图像修复与自动水印移除、实时人脸识别应用、健身智能引体向上计数以及条码识别等功能。这些案例不仅展示了深度学习的强大能力,还证明了QT在跨平台GUI开发中的优势[^2]。 ##### 案例三:树叶识别系统 本研究设计了一个基于深度学习的树叶识别系统,采用PyQt5作为界面开发工具。通过训练大量树叶图像数据,系统能够高效准确地识别不同种类的树叶。这一案例体现了深度学习算法在生态学研究、环境保护等领域的实际应用价值[^3]。 ##### 案例四:YOLOv5、U-Net与CenterNet的应用指南 该项目探索了如何将YOLOv5、U-Net与CenterNet等深度学习模型与PyQt5结合,开发出多模态分析应用、自定义训练循环界面以及教育软件等多种类型的机器学习应用。开发者可以通过此类项目,进一步扩展深度学习与GUI应用的融合范围。 ##### 案例五:简易深度学习标注软件 此软件基于PyQt6和OpenCV开发,提供了简单的深度学习数据标注功能。用户可以通过图形界面快速完成数据标注任务,从而提高深度学习模型的训练效率。该案例详细列出了所需的Python库版本,确保开发环境的稳定性和兼容性[^5]。 ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值