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