生產數據管理系統說明
系統概述
這是一個基于PyQt5和pyodbc開發的生產數據管理系統,主要用于管理生產過程中的物料綁定和查詢操作。系統提供了上料綁定和下料查詢功能,支持與SQL Server數據庫交互,實現數據的插入、查詢、更新和刪除操作。界面采用標簽頁設計,操作流程清晰,反饋明確,適合生產環境使用。
主要功能模塊
系統包含以下核心模塊:
-
數據庫管理模塊(DatabaseManager)
- 負責與SQL Server數據庫建立連接和交互
- 實現數據插入、查詢、更新和刪除操作
- 提供表和列的存在性檢查功能
- 記錄數據庫操作日志
-
上料綁定模塊(MaterialBindingWidget)
- 常溫上料綁定
- 高溫上料插管座綁定
- 高溫上料拔管座綁定
- 支持掃碼信息和批次信息錄入
- 數據查詢和結果展示
- 右鍵刪除記錄功能
-
下料查詢模塊(QueryWidget)
- 掃碼查詢下料記錄
- 結果以表格形式展示
- 按時間由近到遠排序
- 無結果時顯示友好提示
-
日志管理模塊(Logger)
- 全局日志記錄
- 帶時間戳的日志消息
- 日志自動清理功能
- 日志顯示在主界面底部
界面設計特點
-
標簽頁布局
- 四個主要功能標簽頁:常溫上料綁定、高溫上料插管座綁定、高溫上料拔管座綁定、下料查詢
- 界面整潔,功能分區明確
-
按鈕設計
- 綠色提交按鈕(#4CAF50)表示正向操作
- 黃色按鈕表示中間狀態
- 紅色按鈕表示錯誤狀態
- 按鈕懸停和按下狀態有視覺反饋
-
結果展示
- 表格形式展示查詢結果
- 列寬自動適應內容
- 支持右鍵刪除記錄
-
操作反饋
- 提交操作后顯示明確的狀態提示
- 操作過程中按鈕狀態變化
- 日志區域實時記錄操作信息
數據庫操作流程
-
數據插入流程
- 先更新原有數據狀態
- 再插入新數據
- 提交事務
- 記錄操作日志
-
數據查詢流程
- 構建查詢條件
- 執行SQL查詢
- 按時間降序排列結果
- 填充到表格中展示
-
數據刪除流程
- 右鍵選擇記錄
- 確認刪除操作
- 執行刪除SQL
- 提交事務
- 重新查詢刷新結果
顏色說明
-
綠色(#4CAF50)
- 用于表示成功狀態
- 提交按鈕背景色
- 成功提示文字顏色
-
黃色(#FFC107)
- 用于表示執行中狀態
- 中間狀態按鈕背景色
-
紅色(#F44336)
- 用于表示錯誤狀態
- 失敗提示文字和按鈕顏色
-
綠色(#008000)
- 用于標題文字
- 表示正常狀態
特殊功能說明
-
狀態提示按鈕
- 提交操作時按鈕狀態變化:初始 -> 執行中 -> 成功/失敗
- 視覺反饋清晰,操作狀態一目了然
-
自動清空日志
- 當日志行數超過10行且滾動條在底部時自動清空
- 保留最新的日志記錄
-
掃碼規則檢查
- 自動檢查掃碼信息長度
- 長度小于10時提示不符合規則
-
日期范圍查詢
- 默認為最近7天的數據
- 支持自定義日期范圍查詢
使用說明
-
數據庫配置
- 在MainWindow類中修改db_config字典中的服務器地址、數據庫名稱、用戶名和密碼
-
上料綁定
- 輸入掃碼信息和批次信息
- 點擊"提交"按鈕綁定數據
- 按鈕狀態顯示操作結果
- 可在查詢區域查詢歷史記錄
-
下料查詢
- 輸入掃碼信息
- 點擊"查詢"按鈕
- 結果顯示在表格中,按時間由近到遠排列
-
日志查看
- 主界面底部顯示系統操作日志
- 包含時間戳和操作詳情
技術要點
-
數據庫連接管理
- 使用pyodbc連接SQL Server
- 自動重連機制
- 事務管理確保數據一致性
-
界面交互
- 使用信號與槽機制
- 表格組件的上下文菜單
- 輸入框回車提交功能
-
代碼結構
- 面向對象設計,模塊職責明確
- 日志系統獨立封裝
- 界面組件繼承復用
這個生產數據管理系統適合在生產環境中使用,提供了完整的物料綁定和查詢功能,界面友好,操作簡單,同時具備完善的日志記錄和錯誤處理機制。
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 datetimeclass 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 = serverself.database = databaseself.username = usernameself.password = passwordself.connection = Noneself.logger = loggerself.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 Trueexcept Exception as e:error_msg = f"數據庫連接錯誤: {str(e)}"self.log_message(f"[錯誤] {error_msg}")return Falsedef 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 = Nonedef execute_query(self, query, params=None):"""執行SQL查詢"""if not self.connection:if not self.connect():raise Exception("無法建立數據庫連接")try:cursor = self.connection.cursor()query_str = queryif params:query_str = f"{query} (參數: {params})"self.log_message(f"[SQL] 執行查詢: {query_str}")cursor.execute(query, params)return cursorexcept Exception as e:error_msg = f"SQL執行錯誤: {str(e)}"self.log_message(f"[錯誤] {error_msg}")raisedef 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}")raisedef 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 Truereturn Falseexcept Exception as e:error_msg = f"更新數據時出錯: {str(e)}"self.log_message(f"[錯誤] {error_msg}")raisedef 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 Truereturn Falseexcept Exception as e:error_msg = f"插入數據時出錯: {str(e)}"self.log_message(f"[錯誤] {error_msg}")raisedef 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 resultsreturn []except Exception as e:error_msg = f"查詢數據時出錯: {str(e)}"self.log_message(f"[錯誤] {error_msg}")raisedef 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 Truereturn Falseexcept Exception as e:error_msg = f"刪除數據時出錯: {str(e)}"self.log_message(f"[錯誤] {error_msg}")raisedef 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] > 0except Exception as e:error_msg = f"檢查表格存在性時出錯: {str(e)}"self.log_message(f"[錯誤] {error_msg}")return Falsedef 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] > 0except Exception as e:error_msg = f"檢查列存在性時出錯: {str(e)}"self.log_message(f"[錯誤] {error_msg}")return Falsedef 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_managerself.table_name = table_nameself.title = titleself.logger = loggerself.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)returnif not barcode or not batch:self.log_message("[警告] 掃碼信息和批次信息不能為空!")returnself.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_managerself.logger = loggerself.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, "警告", "掃碼信息不能為空!")returntry: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_())