Files
dtm-py-all/UI/views/test_plan_view.py

441 lines
18 KiB
Python
Raw 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.

#!/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()