readFileSystem/ui/incoming_inspection_page.py

364 lines
14 KiB
Python
Raw Permalink Normal View History

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
2026-02-28 14:05:55 +08:00
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()
2026-01-26 09:34:42 +08:00
# 默认光标定位到工程号
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
2026-02-28 14:05:55 +08:00
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:
2026-02-28 14:05:55 +08:00
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 = ""
2026-01-26 09:34:42 +08:00
# 清空后光标重新定位到工程号
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
2026-02-28 14:05:55 +08:00
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:
2026-02-28 14:05:55 +08:00
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))