336 lines
13 KiB
Python
336 lines
13 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
DUT表单对话框(全局复用)
|
||
- 新建/编辑公用一个对话框,根据模式禁用部分字段
|
||
- 数据以 dict(dut_form) 形式在模块间传递
|
||
- 设备分配为二级选择:机台 -> 通道
|
||
"""
|
||
|
||
from PyQt5.QtWidgets import (
|
||
QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QLineEdit, QComboBox,
|
||
QTextEdit, QPushButton, QLabel, QWidget
|
||
)
|
||
from PyQt5.QtCore import Qt
|
||
import requests
|
||
|
||
# 尝试导入配置
|
||
try:
|
||
from config import USE_HTTP_API, HTTP_API_BASE_URL
|
||
except ImportError:
|
||
USE_HTTP_API = False
|
||
HTTP_API_BASE_URL = "http://127.0.0.1:5050"
|
||
|
||
|
||
class DUTFormDialog(QDialog):
|
||
def __init__(self, mode="create", dut_form=None, lock_fields=None, system_model=None, parent=None):
|
||
super().__init__(parent)
|
||
self.mode = mode # "create" | "edit"
|
||
self.dut_form = dut_form or {}
|
||
self.lock_fields = set(lock_fields or [])
|
||
self.system_model = system_model # 用于加载机台数据
|
||
self.setWindowTitle("编辑试验样品详情" if self.mode == "edit" else "新建试验样品")
|
||
self.setModal(True)
|
||
self.resize(800, 480)
|
||
|
||
# HTTP API 配置
|
||
self.use_http_api = USE_HTTP_API
|
||
self.api_base_url = HTTP_API_BASE_URL
|
||
|
||
# 机台数据缓存
|
||
self.machines = []
|
||
self.current_machine = None
|
||
|
||
# 项目阶段数据缓存
|
||
self.project_phases = [] # 存储从后端获取的项目阶段列表
|
||
|
||
self._build_ui()
|
||
self._load_machines() # 加载机台数据
|
||
self._load_project_phases() # 加载项目阶段数据
|
||
self._fill_form(self.dut_form)
|
||
self._apply_lock()
|
||
|
||
def _build_ui(self):
|
||
layout = QVBoxLayout(self)
|
||
layout.setContentsMargins(20, 20, 20, 20)
|
||
layout.setSpacing(12)
|
||
|
||
#title = QLabel("编辑试验样品详情" if self.mode == "edit" else "新建试验样品详情")
|
||
#title.setObjectName("title")
|
||
#title.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||
#layout.addWidget(title)
|
||
|
||
form = QFormLayout()
|
||
form.setLabelAlignment(Qt.AlignRight)
|
||
form.setFormAlignment(Qt.AlignTop)
|
||
form.setHorizontalSpacing(16)
|
||
form.setVerticalSpacing(12)
|
||
|
||
# 左右两列容器
|
||
row = QHBoxLayout()
|
||
left = QVBoxLayout()
|
||
right = QVBoxLayout()
|
||
|
||
# 左列控件
|
||
self.input_SN = QLineEdit()
|
||
self.input_project = QLineEdit()
|
||
self.input_testReq = QLineEdit()
|
||
self.input_phase = QComboBox()
|
||
self.input_phase.setEditable(True)
|
||
# 项目阶段选项将在 _load_project_phases() 中从后端加载
|
||
|
||
# 右列控件 - 设备分配(二级选择)
|
||
self.input_dtMachine = QComboBox() # 机台选择
|
||
self.input_dtMachine.setEditable(False)
|
||
self.input_dtMachine.addItem("", None) # 空选项
|
||
self.input_dtMachine.currentIndexChanged.connect(self._on_machine_changed)
|
||
|
||
self.input_station = QComboBox() # 通道选择
|
||
self.input_station.setEditable(False)
|
||
self.input_station.addItem("", None) # 空选项
|
||
|
||
self.input_weeks = QLineEdit()
|
||
self.input_workOrder = QLineEdit()
|
||
self.input_inspector = QLineEdit()
|
||
|
||
# 说明
|
||
self.input_description = QTextEdit()
|
||
self.input_description.setPlaceholderText("请输入内容")
|
||
|
||
# 左列布局
|
||
left_form = QFormLayout()
|
||
left_form.setLabelAlignment(Qt.AlignRight)
|
||
left_form.addRow("* 产品序列号", self.input_SN)
|
||
left_form.addRow("项目代码", self.input_project)
|
||
left_form.addRow("项目方案", self.input_testReq)
|
||
left_form.addRow("项目阶段", self.input_phase)
|
||
left.addLayout(left_form)
|
||
|
||
# 右列布局
|
||
right_form = QFormLayout()
|
||
right_form.setLabelAlignment(Qt.AlignRight)
|
||
right_form.addRow("机台选择", self.input_dtMachine)
|
||
right_form.addRow("通道选择", self.input_station)
|
||
right_form.addRow("测试周次", self.input_weeks)
|
||
right_form.addRow("工单名称", self.input_workOrder)
|
||
right_form.addRow("测试人员", self.input_inspector)
|
||
right.addLayout(right_form)
|
||
|
||
row.addLayout(left, 1)
|
||
row.addLayout(right, 1)
|
||
layout.addLayout(row)
|
||
|
||
# 说明一行单独处理
|
||
desc_form = QFormLayout()
|
||
desc_form.setLabelAlignment(Qt.AlignRight)
|
||
desc_form.addRow("说明", self.input_description)
|
||
layout.addLayout(desc_form)
|
||
|
||
# 底部按钮
|
||
btn_row = QHBoxLayout()
|
||
btn_row.addStretch()
|
||
self.btn_submit = QPushButton("修改" if self.mode == "edit" else "保存")
|
||
self.btn_close = QPushButton("关闭")
|
||
btn_row.addWidget(self.btn_submit)
|
||
btn_row.addWidget(self.btn_close)
|
||
layout.addLayout(btn_row)
|
||
|
||
self.btn_submit.clicked.connect(self.accept)
|
||
self.btn_close.clicked.connect(self.reject)
|
||
|
||
def _load_machines(self):
|
||
"""加载机台数据"""
|
||
if not self.system_model:
|
||
return
|
||
|
||
try:
|
||
self.machines = self.system_model.load_machines()
|
||
|
||
# 填充机台下拉框
|
||
self.input_dtMachine.clear()
|
||
self.input_dtMachine.addItem("", None) # 空选项
|
||
|
||
for machine in self.machines:
|
||
sn = machine.get("SN", "")
|
||
label = machine.get("label", "")
|
||
display = f"{sn} - {label}" if label else sn
|
||
self.input_dtMachine.addItem(display, machine)
|
||
except Exception as e:
|
||
print(f"加载机台数据失败: {e}")
|
||
|
||
def _load_project_phases(self):
|
||
"""从后端加载项目阶段数据"""
|
||
try:
|
||
# 尝试使用 HTTP API
|
||
if self.use_http_api:
|
||
try:
|
||
url = f"{self.api_base_url}/dbTableAccess"
|
||
response = requests.get(
|
||
url,
|
||
params={"table": "projectPhase"},
|
||
timeout=5
|
||
)
|
||
response.raise_for_status()
|
||
result = response.json()
|
||
data = result.get("data", [])
|
||
|
||
if data:
|
||
self.project_phases = data
|
||
print(f"从 HTTP API 加载了 {len(data)} 个项目阶段")
|
||
else:
|
||
print("后端返回的项目阶段数据为空")
|
||
self.project_phases = []
|
||
|
||
except Exception as e:
|
||
print(f"HTTP API 加载项目阶段失败: {e},回退到 SystemModel")
|
||
# 回退到使用 SystemModel
|
||
if self.system_model:
|
||
self.project_phases = self.system_model.load_project_phases()
|
||
else:
|
||
self.project_phases = []
|
||
else:
|
||
# 直接使用 SystemModel
|
||
if self.system_model:
|
||
self.project_phases = self.system_model.load_project_phases()
|
||
else:
|
||
self.project_phases = []
|
||
|
||
# 填充下拉框:显示 name,存储 PN 作为 itemData
|
||
self.input_phase.clear()
|
||
for phase in self.project_phases:
|
||
name = phase.get("name", "")
|
||
pn = phase.get("PN", "")
|
||
if name: # 只添加有名称的项
|
||
self.input_phase.addItem(name, pn) # 显示 name,数据存储 PN
|
||
|
||
print(f"项目阶段下拉框已填充 {self.input_phase.count()} 个选项")
|
||
|
||
except Exception as e:
|
||
print(f"加载项目阶段数据失败: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
def _on_machine_changed(self, index):
|
||
"""机台选择变化时更新通道列表"""
|
||
self.input_station.clear()
|
||
self.input_station.addItem("", None) # 空选项
|
||
|
||
machine = self.input_dtMachine.currentData()
|
||
if not machine:
|
||
return
|
||
|
||
self.current_machine = machine
|
||
|
||
# 添加有效的通道(station1-4)
|
||
for i in range(1, 5):
|
||
station_key = f"station{i}"
|
||
status_key = f"status{i}"
|
||
try:
|
||
# 安全获取通道名称,处理None和空值
|
||
station_name = machine.get(station_key)
|
||
if station_name is None:
|
||
continue
|
||
|
||
station_name = str(station_name).strip()
|
||
station_status = machine.get(status_key, "")
|
||
|
||
# 过滤掉空值和 NA/N/A 的通道
|
||
if station_name and station_name.upper() not in ['NA', 'N/A']:
|
||
display = f"{i:02d} - {station_name}"
|
||
if station_status:
|
||
display += f" ({station_status})"
|
||
self.input_station.addItem(display, f"{i:02d}")
|
||
except Exception as e:
|
||
print(f"加载通道数据失败: {e}")
|
||
|
||
def _fill_form(self, f):
|
||
# 将已有数据填充到控件
|
||
self.input_SN.setText(str(f.get("SN", "")))
|
||
self.input_project.setText(str(f.get("project", "")))
|
||
self.input_testReq.setText(str(f.get("testReq", "")))
|
||
|
||
# 项目阶段:f.get("phase") 存储的是 PN 值,需要匹配 itemData
|
||
phase_pn = str(f.get("phase", ""))
|
||
if phase_pn:
|
||
# 查找匹配的 PN
|
||
found = False
|
||
for i in range(self.input_phase.count()):
|
||
item_pn = self.input_phase.itemData(i)
|
||
if item_pn == phase_pn:
|
||
self.input_phase.setCurrentIndex(i)
|
||
found = True
|
||
break
|
||
|
||
# 如果没有找到匹配项,使用可编辑功能填充
|
||
if not found:
|
||
self.input_phase.setEditText(phase_pn)
|
||
|
||
# 设备分配:使用 Model 解析后的 dtMachine 和 station 字段
|
||
dtm = f.get("dtMachine", "").strip()
|
||
station = f.get("station", "").strip()
|
||
|
||
# 选中机台
|
||
if dtm:
|
||
for i in range(self.input_dtMachine.count()):
|
||
machine = self.input_dtMachine.itemData(i)
|
||
if machine and machine.get("SN") == dtm:
|
||
self.input_dtMachine.setCurrentIndex(i)
|
||
break
|
||
|
||
# 选中通道(需要在机台选中后)
|
||
if station:
|
||
for i in range(self.input_station.count()):
|
||
station_num = self.input_station.itemData(i)
|
||
if station_num == station:
|
||
self.input_station.setCurrentIndex(i)
|
||
break
|
||
|
||
self.input_weeks.setText(str(f.get("weeks", "")))
|
||
self.input_workOrder.setText(str(f.get("workOrder", "")))
|
||
self.input_inspector.setText(str(f.get("inspector", "")))
|
||
self.input_description.setPlainText(str(f.get("description", "")))
|
||
|
||
def _apply_lock(self):
|
||
# 根据锁定字段禁用编辑(默认在编辑模式锁定 SN)
|
||
defaults = {"SN"} if self.mode == "edit" else set()
|
||
locked = defaults | self.lock_fields
|
||
if "SN" in locked:
|
||
self.input_SN.setDisabled(True)
|
||
# 注意:设备分配在编辑模式下是可以更改的
|
||
|
||
def get_form_dict(self):
|
||
# 以 dut_form 的结构返回数据
|
||
# 项目阶段:优先获取 itemData(PN值),如果没有则使用用户输入的文本
|
||
phase_pn = self.input_phase.currentData() # 获取当前选中项的 PN
|
||
if phase_pn is None: # 如果是用户手动输入的文本
|
||
phase_pn = self.input_phase.currentText().strip()
|
||
|
||
dut_form = {
|
||
"SN": self.input_SN.text().strip(),
|
||
"project": self.input_project.text().strip(),
|
||
"testReq": self.input_testReq.text().strip(),
|
||
"phase": phase_pn, # 存储 PN 值
|
||
"weeks": int(self.input_weeks.text() or 0),
|
||
"workOrder": self.input_workOrder.text().strip(),
|
||
"inspector": self.input_inspector.text().strip(),
|
||
"description": self.input_description.toPlainText().strip(),
|
||
}
|
||
|
||
# 设备分配:从二级选择中获取 dtMachine 和 station
|
||
# Model 会自动将这两个字段合成为 JSON 字符串存入 stationAssigned
|
||
machine = self.input_dtMachine.currentData()
|
||
station_num = self.input_station.currentData()
|
||
|
||
if machine and station_num:
|
||
dut_form["dtMachine"] = machine.get("SN", "")
|
||
dut_form["station"] = station_num
|
||
else:
|
||
dut_form["dtMachine"] = ""
|
||
dut_form["station"] = ""
|
||
|
||
# 保留可能存在的 id 等字段
|
||
if "id" in self.dut_form:
|
||
dut_form["id"] = self.dut_form["id"]
|
||
return dut_form
|