readFileSystem/ui/incoming_inspection_page.py
2026-02-28 14:05:55 +08:00

364 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
from utils.logger import get_logger, log_timing_context
logger = get_logger("incoming_inspection")
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()
# 默认光标定位到工程号
if "batch_no" in self.inputs:
self.inputs["batch_no"].setFocus()
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
logger.info(f"查询工程号: {gch}")
with log_timing_context(f"query_by_gch({gch})", "incoming_inspection"):
data, err = self.db_manager.query_by_gch(gch)
if err:
logger.warning(f"查询失败: {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 = ""
# 清空后光标重新定位到工程号
if "batch_no" in self.inputs:
self.inputs["batch_no"].setFocus()
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
with log_timing_context("query_standard_range", "incoming_inspection"):
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:
logger.debug(f"标准值解析异常: {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))