441 lines
18 KiB
Python
441 lines
18 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
试验计划视图,用于试验计划的安排与结果查看
|
||
"""
|
||
|
||
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||
QPushButton, QTableWidget, QTableWidgetItem,
|
||
QHeaderView, QCheckBox, QButtonGroup, QGroupBox, QMessageBox, QComboBox, QLineEdit)
|
||
from PyQt5.QtCore import Qt
|
||
from models.dut_model import DUTModel
|
||
from models.system_model import SystemModel
|
||
from views.dut_form_dialog import DUTFormDialog
|
||
from ui_utils.user_session import UserSession
|
||
|
||
|
||
class TestPlanView(QWidget):
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.dut_model = DUTModel()
|
||
self.system_model = SystemModel() # 用于加载机台数据
|
||
self.current_page = 1
|
||
self.page_size = 20
|
||
self.search_project = '' # 项目代码筛选
|
||
self.search_sn = '' # 样品SN筛选
|
||
self.init_ui()
|
||
self.load_data()
|
||
|
||
def showEvent(self, event):
|
||
"""每次显示视图时重新加载数据"""
|
||
super().showEvent(event)
|
||
# 重新从后端API加载数据
|
||
print("[测试计划] 视图显示,重新加载数据...")
|
||
self.dut_model._load_duts() # 强制从API重新加载
|
||
self.load_data() # 刷新界面
|
||
|
||
def init_ui(self):
|
||
"""初始化界面"""
|
||
layout = QVBoxLayout(self)
|
||
layout.setContentsMargins(10, 10, 10, 10)
|
||
layout.setSpacing(10)
|
||
|
||
# 标题
|
||
title_label = QLabel("试验计划")
|
||
title_label.setObjectName("title")
|
||
layout.addWidget(title_label)
|
||
|
||
# 搜索筛选栏(项目代码和样品SN)
|
||
search_layout = QHBoxLayout()
|
||
search_layout.setSpacing(10)
|
||
|
||
# 项目代码搜索
|
||
project_label = QLabel("项目代码:")
|
||
search_layout.addWidget(project_label)
|
||
self.project_input = QLineEdit()
|
||
self.project_input.setPlaceholderText("输入项目代码")
|
||
self.project_input.setFixedWidth(150)
|
||
self.project_input.textChanged.connect(self.on_search_changed)
|
||
search_layout.addWidget(self.project_input)
|
||
|
||
# 样品SN搜索
|
||
sn_label = QLabel("样品SN:")
|
||
search_layout.addWidget(sn_label)
|
||
self.sn_input = QLineEdit()
|
||
self.sn_input.setPlaceholderText("输入样品SN")
|
||
self.sn_input.setFixedWidth(200)
|
||
self.sn_input.textChanged.connect(self.on_search_changed)
|
||
search_layout.addWidget(self.sn_input)
|
||
|
||
# 清空按钮
|
||
clear_btn = QPushButton("清空")
|
||
clear_btn.clicked.connect(self.on_clear_search)
|
||
search_layout.addWidget(clear_btn)
|
||
|
||
search_layout.addStretch()
|
||
layout.addLayout(search_layout)
|
||
|
||
# 顶部筛选栏
|
||
filter_layout = QHBoxLayout()
|
||
filter_layout.setSpacing(20)
|
||
|
||
self.filter_btns = {}
|
||
self.filter_group = QButtonGroup()
|
||
self.filter_group.setExclusive(True)
|
||
|
||
filters = ['待安排', '已取消', '待启动', '全部']
|
||
for idx, f in enumerate(filters):
|
||
btn = QCheckBox(f)
|
||
if f == '全部':
|
||
btn.setChecked(True)
|
||
btn.stateChanged.connect(lambda state, name=f: self.on_filter_change(name, state))
|
||
self.filter_btns[f] = btn
|
||
self.filter_group.addButton(btn, idx)
|
||
filter_layout.addWidget(btn)
|
||
|
||
filter_layout.addStretch()
|
||
|
||
# 新增、删除、复制按钮
|
||
self.btn_new = QPushButton("新增")
|
||
self.btn_new.clicked.connect(self.on_new_plan)
|
||
filter_layout.addWidget(self.btn_new)
|
||
|
||
self.btn_delete = QPushButton("删除")
|
||
self.btn_delete.clicked.connect(self.on_delete_plan)
|
||
filter_layout.addWidget(self.btn_delete)
|
||
|
||
self.btn_copy = QPushButton("复制")
|
||
self.btn_copy.clicked.connect(self.on_copy_plan)
|
||
filter_layout.addWidget(self.btn_copy)
|
||
|
||
self.btn_edit = QPushButton("编辑")
|
||
self.btn_edit.clicked.connect(self.on_edit_plan)
|
||
filter_layout.addWidget(self.btn_edit)
|
||
|
||
layout.addLayout(filter_layout)
|
||
|
||
# 数据表格
|
||
self.table = QTableWidget()
|
||
self.table.setColumnCount(11)
|
||
self.table.setHorizontalHeaderLabels([
|
||
"序号", "产品序列号", "项目代码", "项目阶段", "项目方案",
|
||
"测试周次", "测试设备", "试验状态", "当前试验方向", "已完成方向数", "完成日期"
|
||
])
|
||
|
||
# 表格样式
|
||
self.table.setSelectionBehavior(QTableWidget.SelectRows)
|
||
self.table.setSelectionMode(QTableWidget.SingleSelection)
|
||
self.table.setAlternatingRowColors(True)
|
||
self.table.horizontalHeader().setStretchLastSection(True)
|
||
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
|
||
self.table.verticalHeader().setVisible(False)
|
||
self.table.setEditTriggers(QTableWidget.NoEditTriggers)
|
||
|
||
layout.addWidget(self.table)
|
||
|
||
# 分页控制栏
|
||
pager_layout = QHBoxLayout()
|
||
pager_layout.setSpacing(10)
|
||
|
||
self.page_info_label = QLabel("")
|
||
pager_layout.addWidget(self.page_info_label)
|
||
pager_layout.addStretch()
|
||
|
||
QLabel_page_size = QLabel("每页行数:")
|
||
pager_layout.addWidget(QLabel_page_size)
|
||
self.page_size_combo = QComboBox()
|
||
self.page_size_combo.addItems(["20", "50", "100", "200"])
|
||
self.page_size_combo.setCurrentText(str(self.page_size))
|
||
self.page_size_combo.currentTextChanged.connect(self.on_page_size_changed)
|
||
pager_layout.addWidget(self.page_size_combo)
|
||
|
||
self.prev_btn = QPushButton("上一页")
|
||
self.prev_btn.clicked.connect(self.go_prev_page)
|
||
pager_layout.addWidget(self.prev_btn)
|
||
|
||
self.next_btn = QPushButton("下一页")
|
||
self.next_btn.clicked.connect(self.go_next_page)
|
||
pager_layout.addWidget(self.next_btn)
|
||
|
||
layout.addLayout(pager_layout)
|
||
|
||
def on_search_changed(self):
|
||
"""搜索条件变化"""
|
||
self.search_project = self.project_input.text().strip()
|
||
self.search_sn = self.sn_input.text().strip()
|
||
self.current_page = 1 # 重置到第一页
|
||
self.load_data()
|
||
|
||
def on_clear_search(self):
|
||
"""清空搜索条件"""
|
||
self.project_input.clear()
|
||
self.sn_input.clear()
|
||
self.search_project = ''
|
||
self.search_sn = ''
|
||
self.current_page = 1
|
||
self.load_data()
|
||
|
||
def load_data(self):
|
||
"""加载数据并刷新分页"""
|
||
# 根据当前筛选条件
|
||
current_filter = '全部'
|
||
for name, btn in self.filter_btns.items():
|
||
if btn.isChecked():
|
||
current_filter = name
|
||
break
|
||
|
||
plans = self.dut_model.filterByStatus(current_filter if current_filter != '全部' else '')
|
||
|
||
# 应用项目代码和样品SN筛选
|
||
filtered = []
|
||
for plan in plans:
|
||
# 项目代码筛选
|
||
if self.search_project:
|
||
project = str(plan.get('project', '')).lower()
|
||
if self.search_project.lower() not in project:
|
||
continue
|
||
|
||
# 样品SN筛选
|
||
if self.search_sn:
|
||
sn = str(plan.get('SN', '')).lower()
|
||
if self.search_sn.lower() not in sn:
|
||
continue
|
||
|
||
filtered.append(plan)
|
||
|
||
self.filtered_plans = filtered
|
||
|
||
# 计算总页数并规范当前页
|
||
total = len(self.filtered_plans)
|
||
total_pages = max(1, (total + self.page_size - 1) // self.page_size)
|
||
if self.current_page > total_pages:
|
||
self.current_page = total_pages
|
||
if self.current_page < 1:
|
||
self.current_page = 1
|
||
|
||
# 刷新表格与分页信息
|
||
self.refresh_table()
|
||
self.update_pagination_info()
|
||
self.table.resizeColumnsToContents()
|
||
|
||
def on_filter_change(self, name, state):
|
||
"""筛选条件改变"""
|
||
if state == Qt.Checked:
|
||
for n, btn in self.filter_btns.items():
|
||
if n != name:
|
||
btn.setChecked(False)
|
||
self.load_data()
|
||
|
||
def on_new_plan(self):
|
||
"""新增试验计划"""
|
||
dlg = DUTFormDialog(mode="create", dut_form={}, system_model=self.system_model, parent=self)
|
||
if dlg.exec_() == dlg.Accepted:
|
||
form = dlg.get_form_dict()
|
||
# 补充非表单字段的默认值
|
||
form.update({
|
||
'name': '', 'projectType': '', 'itemOnGoing': '', 'itemsFinished': 0,
|
||
'status': '', 'deadLine': '', 'createdate': '', 'direction_codes': ''
|
||
})
|
||
self.dut_model.addDUT(form)
|
||
self.load_data()
|
||
|
||
def on_edit_plan(self):
|
||
"""编辑选中试验计划"""
|
||
row = self.table.currentRow()
|
||
if row < 0 or row >= len(getattr(self, 'current_plans', [])):
|
||
QMessageBox.warning(self, "编辑", "请先选中一行")
|
||
return
|
||
current = self.current_plans[row]
|
||
dlg = DUTFormDialog(mode="edit", dut_form=current, system_model=self.system_model, parent=self)
|
||
if dlg.exec_() == dlg.Accepted:
|
||
form = dlg.get_form_dict()
|
||
# 保留原有未在表单中的字段
|
||
for k in ['name','projectType','itemOnGoing','itemsFinished','status','deadLine','createdate','direction_codes']:
|
||
if k not in form:
|
||
form[k] = current.get(k,'')
|
||
self.dut_model.updateDUT(form)
|
||
self.load_data()
|
||
|
||
def on_delete_plan(self):
|
||
"""删除试验计划"""
|
||
row = self.table.currentRow()
|
||
if row < 0 or row >= len(getattr(self, 'current_plans', [])):
|
||
QMessageBox.warning(self, "删除", "请先选中一行")
|
||
return
|
||
plan_id = self.current_plans[row].get('id')
|
||
if not plan_id:
|
||
QMessageBox.warning(self, "删除", "当前行缺少ID,无法删除")
|
||
return
|
||
reply = QMessageBox.question(self, "确认删除", "确定要删除选中的试验计划吗?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||
if reply != QMessageBox.Yes:
|
||
return
|
||
self.dut_model.removeDUT(plan_id)
|
||
self.load_data()
|
||
|
||
def on_copy_plan(self):
|
||
"""复制试验计划"""
|
||
row = self.table.currentRow()
|
||
if row < 0 or row >= len(getattr(self, 'current_plans', [])):
|
||
QMessageBox.warning(self, "复制", "请先选中一行")
|
||
return
|
||
copy_plan = dict(self.current_plans[row])
|
||
copy_plan.pop('id', None)
|
||
self.dut_model.addDUT(copy_plan)
|
||
self.load_data()
|
||
|
||
def refresh_table(self):
|
||
"""按当前页与每页条数刷新表格"""
|
||
total = len(getattr(self, 'filtered_plans', []))
|
||
start = (self.current_page - 1) * self.page_size
|
||
end = min(start + self.page_size, total)
|
||
page_items = [] if total == 0 else self.filtered_plans[start:end]
|
||
|
||
self.current_plans = page_items
|
||
self.table.setRowCount(len(self.current_plans))
|
||
|
||
# 获取当前用户角色
|
||
user_role = UserSession.get_role()
|
||
can_edit_status = user_role in ['admin', 'super']
|
||
|
||
for idx, plan in enumerate(self.current_plans):
|
||
self.table.setItem(idx, 0, QTableWidgetItem(str(plan.get('index', idx+1))))
|
||
self.table.setItem(idx, 1, QTableWidgetItem(plan.get('SN', '')))
|
||
self.table.setItem(idx, 2, QTableWidgetItem(plan.get('project', '')))
|
||
self.table.setItem(idx, 3, QTableWidgetItem(plan.get('projectPhase', '')))
|
||
self.table.setItem(idx, 4, QTableWidgetItem(plan.get('testReq', '')))
|
||
self.table.setItem(idx, 5, QTableWidgetItem(str(plan.get('weeks', ''))))
|
||
# 测试设备(带颜色)
|
||
station_item = QTableWidgetItem(plan.get('stationAssigned', ''))
|
||
station_text = plan.get('stationAssigned', '').lower()
|
||
if 'com10' in station_text or 'com9' in station_text or 'com6' in station_text:
|
||
station_item.setBackground(Qt.magenta)
|
||
elif 'com7' in station_text or 'com1' in station_text:
|
||
station_item.setBackground(Qt.green)
|
||
elif 'com8' in station_text or 'com3' in station_text:
|
||
station_item.setBackground(Qt.cyan)
|
||
else:
|
||
station_item.setBackground(Qt.blue)
|
||
self.table.setItem(idx, 6, station_item)
|
||
|
||
# 试验状态 - 使用下拉框(仅admin和super可编辑)
|
||
status = plan.get('status', '')
|
||
status_combo = QComboBox()
|
||
status_combo.addItems(['待安排', '试验中', '暂停', '已完成', '已取消'])
|
||
|
||
# 设置当前状态
|
||
if status in ['待安排', '试验中', '暂停', '已完成', '已取消']:
|
||
status_combo.setCurrentText(status)
|
||
else:
|
||
# 处理旧状态名称
|
||
status_map = {
|
||
'待启动': '待安排',
|
||
'暂停中': '暂停'
|
||
}
|
||
new_status = status_map.get(status, '待安排')
|
||
status_combo.setCurrentText(new_status)
|
||
|
||
# 根据状态设置背景色
|
||
current_status = status_combo.currentText()
|
||
if current_status == '已取消':
|
||
status_combo.setStyleSheet("background-color: #d3d3d3;")
|
||
elif current_status == '试验中':
|
||
status_combo.setStyleSheet("background-color: #87CEEB;")
|
||
elif current_status == '暂停':
|
||
status_combo.setStyleSheet("background-color: #FFFF99;")
|
||
elif current_status == '已完成':
|
||
status_combo.setStyleSheet("background-color: #90EE90;")
|
||
else:
|
||
status_combo.setStyleSheet("background-color: white;")
|
||
|
||
# 设置是否可编辑
|
||
status_combo.setEnabled(can_edit_status)
|
||
|
||
# 绑定状态变化事件
|
||
status_combo.setProperty('row', idx)
|
||
status_combo.setProperty('plan_sn', plan.get('SN', ''))
|
||
status_combo.currentTextChanged.connect(self.on_status_changed)
|
||
|
||
self.table.setCellWidget(idx, 7, status_combo)
|
||
|
||
self.table.setItem(idx, 8, QTableWidgetItem(plan.get('currentDirection', '')))
|
||
self.table.setItem(idx, 9, QTableWidgetItem(str(plan.get('itemsFinished', ''))))
|
||
self.table.setItem(idx, 10, QTableWidgetItem(plan.get('deadLine', '')))
|
||
self.table.resizeColumnsToContents()
|
||
|
||
def on_status_changed(self, new_status):
|
||
"""状态下拉框变化事件"""
|
||
sender = self.sender()
|
||
row = sender.property('row')
|
||
plan_sn = sender.property('plan_sn')
|
||
|
||
if row is None or not plan_sn:
|
||
return
|
||
|
||
# 获取当前计划数据
|
||
if row >= len(self.current_plans):
|
||
return
|
||
|
||
plan = self.current_plans[row]
|
||
old_status = plan.get('status', '')
|
||
|
||
# 如果状态未变化,不处理
|
||
if old_status == new_status:
|
||
return
|
||
|
||
# 更新数据
|
||
plan['status'] = new_status
|
||
|
||
try:
|
||
# 调用model更新
|
||
self.dut_model.updateDUT(plan)
|
||
print(f"[状态更新] 样品SN: {plan_sn}, 旧状态: {old_status}, 新状态: {new_status}")
|
||
|
||
# 更新下拉框背景色
|
||
if new_status == '已取消':
|
||
sender.setStyleSheet("background-color: #d3d3d3;")
|
||
elif new_status == '试验中':
|
||
sender.setStyleSheet("background-color: #87CEEB;")
|
||
elif new_status == '暂停':
|
||
sender.setStyleSheet("background-color: #FFFF99;")
|
||
elif new_status == '已完成':
|
||
sender.setStyleSheet("background-color: #90EE90;")
|
||
else:
|
||
sender.setStyleSheet("background-color: white;")
|
||
|
||
except Exception as e:
|
||
QMessageBox.warning(self, "错误", f"更新状态失败:{str(e)}")
|
||
# 恢复为旧状态
|
||
sender.setCurrentText(old_status)
|
||
|
||
def update_pagination_info(self):
|
||
total = len(getattr(self, 'filtered_plans', []))
|
||
total_pages = max(1, (total + self.page_size - 1) // self.page_size)
|
||
self.page_info_label.setText(f"共 {total} 条,{self.current_page}/{total_pages} 页,每页 {self.page_size} 条")
|
||
self.prev_btn.setEnabled(self.current_page > 1)
|
||
self.next_btn.setEnabled(self.current_page < total_pages)
|
||
|
||
def on_page_size_changed(self, text):
|
||
try:
|
||
self.page_size = int(text)
|
||
except Exception:
|
||
self.page_size = 20
|
||
self.current_page = 1
|
||
self.refresh_table()
|
||
self.update_pagination_info()
|
||
|
||
def go_prev_page(self):
|
||
if self.current_page > 1:
|
||
self.current_page -= 1
|
||
self.refresh_table()
|
||
self.update_pagination_info()
|
||
|
||
def go_next_page(self):
|
||
total = len(getattr(self, 'filtered_plans', []))
|
||
total_pages = max(1, (total + self.page_size - 1) // self.page_size)
|
||
if self.current_page < total_pages:
|
||
self.current_page += 1
|
||
self.refresh_table()
|
||
self.update_pagination_info()
|
||
|