from PySide6.QtWidgets import ( QWidget, QTableWidget, QTableWidgetItem, QHeaderView, QLineEdit, QAbstractItemView ) from PySide6.QtCore import Qt, QTimer from openpyxl.styles.builtins import headline_1 from ui.base_page import BaseInspectionPage from src.db_manager import DatabaseManager class IncomingInspectionPage(BaseInspectionPage): def __init__(self, parent=None): super().__init__("入检", parent) self.inputs = {} self.db_manager = DatabaseManager() self.scan_timer = QTimer() self.scan_timer.setSingleShot(True) self.scan_timer.timeout.connect(self.query_data) self.last_gch = "" self.init_ui() def init_ui(self): self.content_layout.setContentsMargins(5, 5, 5, 5) # Initialize Table - 使用4列 self.table = QTableWidget() self.table.setColumnCount(4) self.table.verticalHeader().setVisible(False) self.table.horizontalHeader().setVisible(False) # Style self.table.setStyleSheet(""" QTableWidget { border: 1px solid #999; gridline-color: #999; background-color: white; font-family: 'Microsoft YaHei'; font-size: 18px; } QTableWidget::item { padding: 5px; } QLineEdit { border: none; background-color: transparent; font-family: 'Microsoft YaHei'; font-size: 18px; } """) header = self.table.horizontalHeader() header.setSectionResizeMode(QHeaderView.Stretch) self.table.setSelectionMode(QAbstractItemView.NoSelection) self.table.setFocusPolicy(Qt.NoFocus) self.content_layout.addWidget(self.table) self.render_form() def render_form(self): # 1. 第一行 - 工程号 self.add_row_label_input("工程号", "batch_no", span_input=3) self.inputs["batch_no"].setPlaceholderText("扫描/输入") self.inputs["batch_no"].returnPressed.connect(self.query_data) self.inputs["batch_no"].textChanged.connect(self.on_text_changed) # 2. 第二行 - 产地和规格 row = self.table.rowCount() self.table.insertRow(row) self.table.setRowHeight(row, 50) self.set_label_cell(row, 0, "产地") self.table.setCellWidget(row, 1, self.create_input("material")) self.set_label_cell(row, 2, "规格") self.table.setCellWidget(row, 3, self.create_input("spec")) # 3. 分组标题 - 第一组 self.add_group_header("━ 第一组 ━") # 4. 炉号和材质(第一组) row = self.table.rowCount() self.table.insertRow(row) self.table.setRowHeight(row, 50) self.set_label_cell(row, 0, "炉号", align_left=True) inp1 = self.create_input("material1") self.table.setCellWidget(row, 1, inp1) self.set_label_cell(row, 2, "材质") inp2 = self.create_input("spec1") self.table.setCellWidget(row, 3, inp2) # 5. 检验信息(第一组) self.add_row_label_input("检验信息", "insp_info_1", span_input=3, align_left=True) # 6. 分组标题 - 第二组 self.add_group_header("━ 第二组 ━") # 7. 炉号和材质(第二组) row = self.table.rowCount() self.table.insertRow(row) self.table.setRowHeight(row, 50) self.set_label_cell(row, 0, "炉号", align_left=True) inp3 = self.create_input("info1") self.table.setCellWidget(row, 1, inp3) self.set_label_cell(row, 2, "材质") inp4 = self.create_input("spec2") self.table.setCellWidget(row, 3, inp4) # 8. 检验信息(第二组) self.add_row_label_input("检验信息", "insp_info_2", span_input=3, align_left=True) self.add_group_header("━ 检验值 ━") # 9. 元素行 - 固定顺序显示所有18个元素 element_order = ["C", "Si", "Mn", "P", "S", "Cr", "Ni", "Mo", "Cu", "Ti", "Nb", "V", "Al", "W", "Co", "N", "Fe", "Se"] for i in range(0, len(element_order), 2): name1 = element_order[i] name2 = element_order[i+1] if i + 1 < len(element_order) else None row = self.table.rowCount() self.table.insertRow(row) self.table.setRowHeight(row, 50) self.set_label_cell(row, 0, name1) self.table.setCellWidget(row, 1, self.create_input(name1)) if name2: self.set_label_cell(row, 2, name2) self.table.setCellWidget(row, 3, self.create_input(name2)) def add_row_label_input(self, label_text, key, span_input=1, bg_color=None, group_col=True, align_left=False): row = self.table.rowCount() self.table.insertRow(row) self.table.setRowHeight(row, 50) if not group_col: # 不需要分组列,第一列空白 self.set_empty_cell(row, 0) self.set_label_cell(row, 1, label_text, bg_color=bg_color, align_left=align_left) inp = self.create_input(key) self.table.setCellWidget(row, 2, inp) if span_input > 1: self.table.setSpan(row, 2, 1, span_input) else: self.set_label_cell(row, 0, label_text, bg_color=bg_color, align_left=align_left) inp = self.create_input(key) self.table.setCellWidget(row, 1, inp) if span_input > 1: self.table.setSpan(row, 1, 1, span_input) if bg_color: col_start = 2 if not group_col else 1 for col in range(col_start, col_start + span_input): self.set_cell_background(row, col, bg_color) def set_group_cell(self, row, col, text, rowspan=1, bg_color=None): """设置分组标识单元格""" item = QTableWidgetItem(text) item.setTextAlignment(Qt.AlignCenter) item.setFlags(Qt.ItemIsEnabled) from PySide6.QtGui import QColor, QBrush, QFont if bg_color: brush = QBrush(QColor(bg_color)) else: brush = QBrush(QColor("#f5f5f5")) item.setBackground(brush) font = QFont() font.setBold(True) font.setPointSize(16) item.setFont(font) self.table.setItem(row, col, item) if rowspan > 1: self.table.setSpan(row, col, rowspan, 1) def set_empty_cell(self, row, col): """设置空白单元格""" item = QTableWidgetItem("") item.setFlags(Qt.ItemIsEnabled) self.table.setItem(row, col, item) def add_group_header(self, text): """添加分组标题行""" row = self.table.rowCount() self.table.insertRow(row) self.table.setRowHeight(row, 40) item = QTableWidgetItem(text) item.setTextAlignment(Qt.AlignCenter) item.setFlags(Qt.ItemIsEnabled) from PySide6.QtGui import QColor, QBrush, QFont brush = QBrush(QColor("#f0f0f0")) item.setBackground(brush) font = QFont() font.setBold(True) font.setPointSize(14) item.setFont(font) self.table.setItem(row, 0, item) self.table.setSpan(row, 0, 1, 4) def set_cell_background(self, row, col, color): """设置单元格背景色""" item = self.table.item(row, col) if not item: item = QTableWidgetItem("") self.table.setItem(row, col, item) from PySide6.QtGui import QColor, QBrush brush = QBrush(QColor(color)) item.setBackground(brush) def set_label_cell(self, row, col, text, bg_color=None, align_left=False): item = QTableWidgetItem(text) # 根据参数设置对齐方式 if align_left: item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter) else: item.setTextAlignment(Qt.AlignCenter) item.setFlags(Qt.ItemIsEnabled) from PySide6.QtGui import QColor, QBrush if bg_color: brush = QBrush(QColor(bg_color)) else: brush = QBrush(QColor("#f5f5f5")) item.setBackground(brush) self.table.setItem(row, col, item) def create_input(self, key): inp = QLineEdit() if key in self.inputs: key = f"{key}_2" self.inputs[key] = inp return inp def on_text_changed(self, text): """PDA扫码输入检测,延时300ms后触发查询""" self.scan_timer.stop() if text.strip() and text.strip() != self.last_gch: self.scan_timer.start(300) def query_data(self): self.scan_timer.stop() gch = self.inputs["batch_no"].text().strip() if not gch or gch == self.last_gch: return self.last_gch = gch data, err = self.db_manager.query_by_gch(gch) if err: self.show_info("查询失败", err) return self.inputs["material"].setText(data.get("cd") or "") self.inputs["spec"].setText(data.get("size") or "") self.inputs["material1"].setText(data.get("heat_number") or "") self.inputs["spec1"].setText(data.get("qy_czt") or "") self.inputs["info1"].setText(data.get("heat_number2") or "") self.inputs["spec2"].setText(data.get("qy_czw") or "") def update_ui_with_data(self, data): """ 根据数据库数据更新UI。 """ elements = data.get("elements", {}) # 固定元素顺序 element_order = ["C", "Si", "Mn", "P", "S", "Cr", "Ni", "Mo", "Cu", "Ti", "Nb", "V", "Al", "W", "Co", "N", "Fe", "Se"] # 更新所有元素值,没有的显示为0 for el in element_order: if el in self.inputs: value = "0" if (v := float(elements.get(el, 0))) == 0 else f"{v:.6f}" self.inputs[el].setText(str(value)) # 比对标准值范围 self.validate_elements(elements) def clear_data(self): """清空所有输入框数据""" for inp in self.inputs.values(): inp.clear() self.last_gch = "" def validate_elements(self, elements): """比对元素值与标准范围,不符合的写入检验信息框""" heat_number = self.inputs.get("material1") heat_number2 = self.inputs.get("info1") if not heat_number or not heat_number2: return hn1 = heat_number.text().strip() hn2 = heat_number2.text().strip() if not hn1 and not hn2: return heat1_range, heat2_range, err = self.db_manager.query_standard_range(hn1, hn2) if err: self.show_info("标准值查询失败", err) return # 元素名到数据库字段的映射 (18个元素) element_map = { "C": "c", "Si": "si", "Mn": "mn", "P": "p", "S": "s", "Cr": "cr", "Ni": "ni", "Mo": "mo", "Cu": "cu", "Ti": "ti", "Nb": "nb", "V": "v", "Al": "al", "W": "w", "Co": "co", "N": "n", "Fe": "fe", "Se": "se" } def check_range(std_range, elements_data): """检查元素是否在范围内,返回不符合的列表""" out_of_range = [] if not std_range: return out_of_range for el_name, db_field in element_map.items(): if el_name not in elements_data: continue try: value = float(elements_data[el_name]) except (ValueError, TypeError): continue min_key = f"{db_field}min" max_key = f"{db_field}max" min_val = std_range.get(min_key) max_val = std_range.get(max_key) if min_val is None and max_val is None: continue try: min_val = float(min_val) if min_val is not None and min_val != '' else None max_val = float(max_val) if max_val is not None and max_val != '' else None except (ValueError, TypeError) as e: print(e) is_out = False if min_val is not None and value < min_val: is_out = True if max_val is not None and value > max_val: is_out = True if is_out: min_str = str(min_val) if min_val is not None else "" max_str = str(max_val) if max_val is not None else "" out_of_range.append(f"{el_name}({min_str}-{max_str})") return out_of_range def get_info_text(std_range): if not std_range: return "无标准值" out = check_range(std_range, elements) return "/".join(out) + "/" if out else "" self.inputs["insp_info_1"].setText(get_info_text(heat1_range)) self.inputs["insp_info_2"].setText(get_info_text(heat2_range))