757 lines
27 KiB
Python
757 lines
27 KiB
Python
"""
|
||
传感器测试设备软件 - 主入口
|
||
|
||
基于 PyQt5 构建的桌面图形界面应用。
|
||
提供从登录、测试配置、自动化执行、实时监控到报告生成的全流程操作界面。
|
||
"""
|
||
|
||
import sys
|
||
import time
|
||
import threading
|
||
from datetime import datetime
|
||
from typing import Optional
|
||
|
||
from PyQt5.QtWidgets import (
|
||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||
QLabel, QPushButton, QLineEdit, QTextEdit, QComboBox,
|
||
QTableWidget, QTableWidgetItem, QGroupBox, QFormLayout,
|
||
QMessageBox, QTabWidget, QSplitter, QStatusBar, QHeaderView,
|
||
QProgressBar, QFrame, QGridLayout, QStackedWidget, QDialog,
|
||
QListWidget, QListWidgetItem
|
||
)
|
||
from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal, QObject
|
||
from PyQt5.QtGui import QFont, QPalette, QColor, QPixmap, QPainter, QPen
|
||
|
||
from app.data_structures import (
|
||
UserInfo, UserRole, SensorModelConfig, TestContext,
|
||
JudgmentResult, TestResult, SystemStatus, SystemMode
|
||
)
|
||
from app.services import (
|
||
SecurityService, SensorConfigService, TestEngine,
|
||
CalculationService, ReportService
|
||
)
|
||
|
||
|
||
# =============================================================================
|
||
# 数据采集模拟线程
|
||
# =============================================================================
|
||
|
||
class AcquisitionWorker(QObject):
|
||
"""
|
||
数据采集工作线程
|
||
|
||
在后台线程中模拟硬件数据采集,避免阻塞UI。
|
||
"""
|
||
data_acquired = pyqtSignal(object)
|
||
status_changed = pyqtSignal()
|
||
test_completed = pyqtSignal(object)
|
||
|
||
def __init__(self, engine: TestEngine):
|
||
"""
|
||
初始化采集线程
|
||
|
||
Args:
|
||
engine: 测试引擎实例
|
||
"""
|
||
super().__init__()
|
||
self._engine = engine
|
||
self._running = False
|
||
|
||
def start_acquisition(self) -> None:
|
||
"""开始采集循环"""
|
||
self._running = True
|
||
while self._running:
|
||
ctx = self._engine.context
|
||
if ctx.status_flags["is_running"] and not ctx.status_flags["is_paused"]:
|
||
point = self._engine.simulate_acquisition()
|
||
self.data_acquired.emit(point)
|
||
self.status_changed.emit()
|
||
|
||
# 检查测试是否完成
|
||
if not ctx.status_flags["is_running"]:
|
||
self.test_completed.emit(
|
||
self._engine.context.acquisition_buffer.get_all()
|
||
)
|
||
time.sleep(0.05) # ~20fps
|
||
else:
|
||
time.sleep(0.1)
|
||
|
||
def stop(self) -> None:
|
||
"""停止采集循环"""
|
||
self._running = False
|
||
|
||
|
||
# =============================================================================
|
||
# 登录对话框
|
||
# =============================================================================
|
||
|
||
class LoginDialog(QDialog):
|
||
"""
|
||
登录对话框
|
||
|
||
提供双因子认证(用户名/密码 + TOTP动态口令)登录界面。
|
||
"""
|
||
login_success = pyqtSignal(object)
|
||
|
||
def __init__(self, security_service: SecurityService):
|
||
"""
|
||
初始化登录对话框
|
||
|
||
Args:
|
||
security_service: 安全认证服务实例
|
||
"""
|
||
super().__init__()
|
||
self._security = security_service
|
||
self._init_ui()
|
||
|
||
def _init_ui(self) -> None:
|
||
"""初始化用户界面"""
|
||
self.setWindowTitle("传感器测试设备 - 登录")
|
||
self.setFixedSize(400, 300)
|
||
|
||
layout = QVBoxLayout()
|
||
|
||
# 标题
|
||
title = QLabel("传感器测试设备软件")
|
||
title.setAlignment(Qt.AlignCenter)
|
||
title_font = QFont("Microsoft YaHei", 16, QFont.Bold)
|
||
title.setFont(title_font)
|
||
layout.addWidget(title)
|
||
|
||
layout.addSpacing(20)
|
||
|
||
# 表单
|
||
form = QFormLayout()
|
||
|
||
self.username_input = QLineEdit()
|
||
self.username_input.setPlaceholderText("请输入用户名")
|
||
self.password_input = QLineEdit()
|
||
self.password_input.setPlaceholderText("请输入密码")
|
||
self.password_input.setEchoMode(QLineEdit.Password)
|
||
self.totp_input = QLineEdit()
|
||
self.totp_input.setPlaceholderText("请输入6位动态口令")
|
||
self.totp_input.setMaxLength(6)
|
||
|
||
form.addRow("用户名:", self.username_input)
|
||
form.addRow("密 码:", self.password_input)
|
||
form.addRow("动态口令:", self.totp_input)
|
||
layout.addLayout(form)
|
||
|
||
layout.addSpacing(20)
|
||
|
||
# 登录按钮
|
||
self.login_btn = QPushButton("登 录")
|
||
self.login_btn.setMinimumHeight(36)
|
||
self.login_btn.clicked.connect(self._do_login)
|
||
layout.addWidget(self.login_btn)
|
||
|
||
# 状态显示
|
||
self.status_label = QLabel("")
|
||
self.status_label.setAlignment(Qt.AlignCenter)
|
||
self.status_label.setStyleSheet("color: red;")
|
||
layout.addWidget(self.status_label)
|
||
|
||
self.setLayout(layout)
|
||
|
||
# 默认填充(方便测试)
|
||
self.username_input.setText("op01")
|
||
self.password_input.setText("pass123")
|
||
self.totp_input.setText("123456")
|
||
|
||
def _do_login(self) -> None:
|
||
"""执行登录"""
|
||
username = self.username_input.text().strip()
|
||
password = self.password_input.text()
|
||
totp = self.totp_input.text().strip()
|
||
|
||
if not username or not password or not totp:
|
||
self.status_label.setText("请填写所有字段")
|
||
return
|
||
|
||
success, msg = self._security.login(username, password, totp)
|
||
if success:
|
||
self.login_success.emit(self._security.get_current_user())
|
||
self.accept()
|
||
else:
|
||
self.status_label.setText(msg)
|
||
|
||
|
||
# =============================================================================
|
||
# 主窗口
|
||
# =============================================================================
|
||
|
||
class MainWindow(QMainWindow):
|
||
"""
|
||
传感器测试设备主窗口
|
||
|
||
提供测试任务配置、自动化执行、实时监控、质量判定和报告生成的核心界面。
|
||
"""
|
||
|
||
def __init__(self, user: UserInfo):
|
||
"""
|
||
初始化主窗口
|
||
|
||
Args:
|
||
user: 当前登录用户信息
|
||
"""
|
||
super().__init__()
|
||
self._current_user = user
|
||
|
||
# 初始化服务
|
||
self._security = SecurityService()
|
||
self._security._current_session = user
|
||
self._sensor_config = SensorConfigService()
|
||
self._test_engine = TestEngine(self._sensor_config)
|
||
self._calc_service = CalculationService()
|
||
self._report_service = ReportService()
|
||
|
||
# 采集线程
|
||
self._acq_thread: Optional[QThread] = None
|
||
self._acq_worker: Optional[AcquisitionWorker] = None
|
||
|
||
# 最后判定结果
|
||
self._last_judgment: Optional[JudgmentResult] = None
|
||
|
||
self._init_ui()
|
||
self._setup_timers()
|
||
self._update_permissions()
|
||
|
||
def _init_ui(self) -> None:
|
||
"""初始化主界面"""
|
||
self.setWindowTitle("传感器测试设备软件 - 安全模式")
|
||
self.setGeometry(100, 100, 1200, 800)
|
||
|
||
# 中央部件
|
||
central = QWidget()
|
||
self.setCentralWidget(central)
|
||
main_layout = QVBoxLayout()
|
||
|
||
# 顶部工具栏
|
||
toolbar = QHBoxLayout()
|
||
self.user_label = QLabel(f"用户: {self._current_user.user_id} "
|
||
f"({self._current_user.role.value})")
|
||
self.user_label.setStyleSheet("font-weight: bold; padding: 5px;")
|
||
toolbar.addWidget(self.user_label)
|
||
|
||
toolbar.addStretch()
|
||
|
||
self.status_indicator = QLabel("● 系统正常")
|
||
self.status_indicator.setStyleSheet("color: green; font-weight: bold;")
|
||
toolbar.addWidget(self.status_indicator)
|
||
|
||
self.logout_btn = QPushButton("注销")
|
||
self.logout_btn.clicked.connect(self._do_logout)
|
||
toolbar.addWidget(self.logout_btn)
|
||
|
||
main_layout.addLayout(toolbar)
|
||
|
||
# 主分割器
|
||
splitter = QSplitter(Qt.Horizontal)
|
||
|
||
# 左侧面板
|
||
left_panel = QWidget()
|
||
left_layout = QVBoxLayout()
|
||
|
||
# 扫码/条码输入区
|
||
barcode_group = QGroupBox("条码识别")
|
||
barcode_layout = QVBoxLayout()
|
||
barcode_input_layout = QHBoxLayout()
|
||
self.barcode_input = QLineEdit()
|
||
self.barcode_input.setPlaceholderText("扫码或手动输入传感器条码")
|
||
self.barcode_input.returnPressed.connect(self._on_barcode_input)
|
||
barcode_input_layout.addWidget(self.barcode_input)
|
||
self.load_btn = QPushButton("加载测试计划")
|
||
self.load_btn.clicked.connect(self._on_barcode_input)
|
||
barcode_input_layout.addWidget(self.load_btn)
|
||
barcode_layout.addLayout(barcode_input_layout)
|
||
self.barcode_info = QLabel("等待扫码...")
|
||
barcode_layout.addWidget(self.barcode_info)
|
||
barcode_group.setLayout(barcode_layout)
|
||
left_layout.addWidget(barcode_group)
|
||
|
||
# 控制区
|
||
control_group = QGroupBox("测试控制")
|
||
control_layout = QGridLayout()
|
||
|
||
self.start_btn = QPushButton("▶ 开始")
|
||
self.start_btn.clicked.connect(self._start_test)
|
||
control_layout.addWidget(self.start_btn, 0, 0)
|
||
|
||
self.pause_btn = QPushButton("⏸ 暂停")
|
||
self.pause_btn.clicked.connect(self._pause_test)
|
||
self.pause_btn.setEnabled(False)
|
||
control_layout.addWidget(self.pause_btn, 0, 1)
|
||
|
||
self.stop_btn = QPushButton("⏹ 停止")
|
||
self.stop_btn.clicked.connect(self._stop_test)
|
||
self.stop_btn.setEnabled(False)
|
||
control_layout.addWidget(self.stop_btn, 1, 0)
|
||
|
||
self.skip_btn = QPushButton("⏭ 跳步")
|
||
self.skip_btn.clicked.connect(self._skip_point)
|
||
self.skip_btn.setEnabled(False)
|
||
control_layout.addWidget(self.skip_btn, 1, 1)
|
||
|
||
control_group.setLayout(control_layout)
|
||
left_layout.addWidget(control_group)
|
||
|
||
# 进度区
|
||
progress_group = QGroupBox("测试进度")
|
||
progress_layout = QVBoxLayout()
|
||
|
||
progress_layout.addWidget(QLabel("当前循环:"))
|
||
self.cycle_label = QLabel("0 / 0")
|
||
progress_layout.addWidget(self.cycle_label)
|
||
|
||
progress_layout.addWidget(QLabel("测试点进度:"))
|
||
self.point_progress = QProgressBar()
|
||
self.point_progress.setRange(0, 100)
|
||
self.point_progress.setValue(0)
|
||
progress_layout.addWidget(self.point_progress)
|
||
|
||
progress_layout.addWidget(QLabel("总体进度:"))
|
||
self.total_progress = QProgressBar()
|
||
self.total_progress.setRange(0, 100)
|
||
self.total_progress.setValue(0)
|
||
progress_layout.addWidget(self.total_progress)
|
||
|
||
progress_group.setLayout(progress_layout)
|
||
left_layout.addWidget(progress_group)
|
||
|
||
left_layout.addStretch()
|
||
left_panel.setLayout(left_layout)
|
||
splitter.addWidget(left_panel)
|
||
|
||
# 中间面板 - 实时数据与绘图
|
||
center_panel = QWidget()
|
||
center_layout = QVBoxLayout()
|
||
|
||
# 实时数值显示
|
||
value_group = QGroupBox("实时数据")
|
||
value_layout = QGridLayout()
|
||
|
||
value_layout.addWidget(QLabel("目标压力:"), 0, 0)
|
||
self.target_pressure_label = QLabel("-- MPa")
|
||
self.target_pressure_label.setStyleSheet("font-size: 18px; font-weight: bold;")
|
||
value_layout.addWidget(self.target_pressure_label, 0, 1)
|
||
|
||
value_layout.addWidget(QLabel("实际压力:"), 1, 0)
|
||
self.actual_pressure_label = QLabel("-- MPa")
|
||
self.actual_pressure_label.setStyleSheet("font-size: 18px; font-weight: bold;")
|
||
value_layout.addWidget(self.actual_pressure_label, 1, 1)
|
||
|
||
value_layout.addWidget(QLabel("传感器输出:"), 2, 0)
|
||
self.output_label = QLabel("--")
|
||
self.output_label.setStyleSheet("font-size: 18px; font-weight: bold;")
|
||
value_layout.addWidget(self.output_label, 2, 1)
|
||
|
||
value_layout.addWidget(QLabel("报警状态:"), 3, 0)
|
||
self.alarm_label = QLabel("● NORMAL")
|
||
self.alarm_label.setStyleSheet("color: green; font-size: 16px; font-weight: bold;")
|
||
value_layout.addWidget(self.alarm_label, 3, 1)
|
||
|
||
value_group.setLayout(value_layout)
|
||
center_layout.addWidget(value_group)
|
||
|
||
# 实时绘图区域(简化:使用文本模拟)
|
||
plot_group = QGroupBox("实时曲线图 (压力-输出)")
|
||
plot_layout = QVBoxLayout()
|
||
self.plot_widget = QTextEdit()
|
||
self.plot_widget.setReadOnly(True)
|
||
self.plot_widget.setMaximumHeight(200)
|
||
self.plot_widget.setPlaceholderText("实时曲线数据将在测试过程中显示...")
|
||
plot_layout.addWidget(self.plot_widget)
|
||
plot_group.setLayout(plot_layout)
|
||
center_layout.addWidget(plot_group)
|
||
|
||
# 数据表
|
||
data_group = QGroupBox("采集数据表")
|
||
data_layout = QVBoxLayout()
|
||
self.data_table = QTableWidget(0, 3)
|
||
self.data_table.setHorizontalHeaderLabels(["压力 (MPa)", "输出值", "时间戳"])
|
||
self.data_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||
data_layout.addWidget(self.data_table)
|
||
data_group.setLayout(data_layout)
|
||
center_layout.addWidget(data_group)
|
||
|
||
center_panel.setLayout(center_layout)
|
||
splitter.addWidget(center_panel)
|
||
|
||
# 右侧面板
|
||
right_panel = QWidget()
|
||
right_layout = QVBoxLayout()
|
||
|
||
# 判定结果
|
||
result_group = QGroupBox("判定结果")
|
||
result_layout = QVBoxLayout()
|
||
|
||
self.result_label = QLabel("--")
|
||
self.result_label.setAlignment(Qt.AlignCenter)
|
||
self.result_label.setStyleSheet(
|
||
"font-size: 24px; font-weight: bold; padding: 10px;"
|
||
)
|
||
result_layout.addWidget(self.result_label)
|
||
|
||
result_layout.addWidget(QLabel("非线性误差:"))
|
||
self.nonlinearity_label = QLabel("-- %")
|
||
result_layout.addWidget(self.nonlinearity_label)
|
||
|
||
result_layout.addWidget(QLabel("迟滞误差:"))
|
||
self.hysteresis_label = QLabel("-- %")
|
||
result_layout.addWidget(self.hysteresis_label)
|
||
|
||
result_layout.addWidget(QLabel("重复性误差:"))
|
||
self.repeatability_label = QLabel("-- %")
|
||
result_layout.addWidget(self.repeatability_label)
|
||
|
||
result_layout.addWidget(QLabel("超标详情:"))
|
||
self.details_text = QTextEdit()
|
||
self.details_text.setReadOnly(True)
|
||
self.details_text.setMaximumHeight(60)
|
||
result_layout.addWidget(self.details_text)
|
||
|
||
result_group.setLayout(result_layout)
|
||
right_layout.addWidget(result_group)
|
||
|
||
# 报告生成
|
||
report_group = QGroupBox("报告导出")
|
||
report_layout = QVBoxLayout()
|
||
|
||
self.report_btn = QPushButton("📄 生成报告")
|
||
self.report_btn.clicked.connect(self._generate_report)
|
||
self.report_btn.setEnabled(False)
|
||
report_layout.addWidget(self.report_btn)
|
||
|
||
self.report_status = QLabel("就绪")
|
||
report_layout.addWidget(self.report_status)
|
||
|
||
report_group.setLayout(report_layout)
|
||
right_layout.addWidget(report_group)
|
||
|
||
right_layout.addStretch()
|
||
right_panel.setLayout(right_layout)
|
||
splitter.addWidget(right_panel)
|
||
|
||
main_layout.addWidget(splitter)
|
||
central.setLayout(main_layout)
|
||
|
||
# 状态栏
|
||
self.statusBar().showMessage("系统就绪 | 安全模式已启用")
|
||
|
||
def _setup_timers(self) -> None:
|
||
"""设置定时器"""
|
||
# 状态刷新定时器 (100ms)
|
||
self.status_timer = QTimer()
|
||
self.status_timer.timeout.connect(self._refresh_status)
|
||
self.status_timer.start(100)
|
||
|
||
def _update_permissions(self) -> None:
|
||
"""根据用户权限更新界面"""
|
||
role = self._current_user.role
|
||
|
||
# 工艺员和管理员可以管理配置(简化为界面不可见功能)
|
||
# 操作员只能执行测试
|
||
|
||
def _on_barcode_input(self) -> None:
|
||
"""处理条码输入"""
|
||
barcode = self.barcode_input.text().strip()
|
||
if not barcode:
|
||
return
|
||
|
||
# 查找配置
|
||
config = self._sensor_config.get_config(barcode)
|
||
if config:
|
||
self.barcode_info.setText(
|
||
f"已识别: {config.model_id}\n"
|
||
f"量程: {config.range_min}-{config.range_max} MPa\n"
|
||
f"测试点数: {config.test_points} | 循环: {config.cycles}"
|
||
)
|
||
# 加载测试计划
|
||
if self._test_engine.load_test_plan(barcode):
|
||
self.statusBar().showMessage(f"测试计划已加载: {barcode}")
|
||
self.start_btn.setEnabled(True)
|
||
else:
|
||
self.barcode_info.setText("加载测试计划失败")
|
||
else:
|
||
self.barcode_info.setText(f"未找到型号配置: {barcode}")
|
||
QMessageBox.warning(self, "未识别的条码",
|
||
f"传感器条码 '{barcode}' 未在配置表中找到。")
|
||
|
||
def _start_test(self) -> None:
|
||
"""启动测试"""
|
||
if not self._test_engine.start_test():
|
||
QMessageBox.warning(self, "启动失败", "无法启动测试,请先加载测试计划。")
|
||
return
|
||
|
||
# 启动采集线程
|
||
self._acq_thread = QThread()
|
||
self._acq_worker = AcquisitionWorker(self._test_engine)
|
||
self._acq_worker.moveToThread(self._acq_thread)
|
||
|
||
self._acq_worker.data_acquired.connect(self._on_data_acquired)
|
||
self._acq_worker.status_changed.connect(self._refresh_ui)
|
||
self._acq_worker.test_completed.connect(self._on_test_completed)
|
||
|
||
self._acq_thread.started.connect(self._acq_worker.start_acquisition)
|
||
self._acq_thread.start()
|
||
|
||
# 更新按钮状态
|
||
self.start_btn.setEnabled(False)
|
||
self.pause_btn.setEnabled(True)
|
||
self.stop_btn.setEnabled(True)
|
||
self.skip_btn.setEnabled(True)
|
||
self.load_btn.setEnabled(False)
|
||
|
||
self.statusBar().showMessage("测试进行中...")
|
||
|
||
def _pause_test(self) -> None:
|
||
"""暂停测试"""
|
||
if self._test_engine.pause_test():
|
||
self.pause_btn.setText("▶ 继续")
|
||
self.statusBar().showMessage("测试已暂停")
|
||
else:
|
||
# 如果当前是暂停状态,点击继续
|
||
if self._test_engine.resume_test():
|
||
self.pause_btn.setText("⏸ 暂停")
|
||
self.statusBar().showMessage("测试已继续")
|
||
|
||
def _stop_test(self) -> None:
|
||
"""停止测试"""
|
||
self._test_engine.stop_test()
|
||
if self._acq_worker:
|
||
self._acq_worker.stop()
|
||
if self._acq_thread:
|
||
self._acq_thread.quit()
|
||
self._acq_thread.wait()
|
||
|
||
self.start_btn.setEnabled(True)
|
||
self.pause_btn.setEnabled(False)
|
||
self.stop_btn.setEnabled(False)
|
||
self.skip_btn.setEnabled(False)
|
||
self.load_btn.setEnabled(True)
|
||
self.pause_btn.setText("⏸ 暂停")
|
||
|
||
self.statusBar().showMessage("测试已停止")
|
||
|
||
def _skip_point(self) -> None:
|
||
"""跳步到下一个测试点"""
|
||
ctx = self._test_engine.context
|
||
new_idx = ctx.current_point_index + 1
|
||
if self._test_engine.skip_to_point(new_idx):
|
||
self.statusBar().showMessage(f"跳转到第 {new_idx + 1} 个测试点")
|
||
else:
|
||
self.statusBar().showMessage("跳步失败")
|
||
|
||
def _on_data_acquired(self, point) -> None:
|
||
"""处理采集到的数据点"""
|
||
# 更新实时数值
|
||
self.target_pressure_label.setText(f"{point.pressure:.4f} MPa")
|
||
self.actual_pressure_label.setText(f"{point.pressure:.4f} MPa")
|
||
self.output_label.setText(f"{point.output:.6f}")
|
||
|
||
# 更新报警状态
|
||
ctx = self._test_engine.context
|
||
if ctx.status_flags["alarm_active"]:
|
||
self.alarm_label.setText("● ALARM")
|
||
self.alarm_label.setStyleSheet("color: red; font-size: 16px; font-weight: bold;")
|
||
else:
|
||
self.alarm_label.setText("● NORMAL")
|
||
self.alarm_label.setStyleSheet("color: green; font-size: 16px; font-weight: bold;")
|
||
|
||
def _refresh_ui(self) -> None:
|
||
"""刷新UI状态"""
|
||
ctx = self._test_engine.context
|
||
config = self._sensor_config.get_config(ctx.current_model)
|
||
|
||
# 更新进度
|
||
total_points = len(ctx.pressure_sequence)
|
||
if total_points > 0:
|
||
progress = int(ctx.current_point_index / total_points * 100)
|
||
self.point_progress.setValue(progress)
|
||
|
||
if config:
|
||
total_cycles = config.cycles
|
||
total = total_points * total_cycles
|
||
current = ctx.current_point_index + (ctx.current_cycle - 1) * total_points
|
||
if total > 0:
|
||
overall = int(current / total * 100)
|
||
self.total_progress.setValue(overall)
|
||
|
||
self.cycle_label.setText(f"{ctx.current_cycle} / {total_cycles}")
|
||
|
||
# 更新数据表(仅显示最近10条)
|
||
all_data = ctx.acquisition_buffer.get_all()
|
||
recent = all_data[-20:] if len(all_data) > 20 else all_data
|
||
self.data_table.setRowCount(len(recent))
|
||
for i, dp in enumerate(recent):
|
||
self.data_table.setItem(i, 0, QTableWidgetItem(f"{dp.pressure:.4f}"))
|
||
self.data_table.setItem(i, 1, QTableWidgetItem(f"{dp.output:.6f}"))
|
||
self.data_table.setItem(i, 2, QTableWidgetItem(f"{dp.timestamp:.3f}"))
|
||
|
||
# 更新曲线文本
|
||
if len(all_data) > 0:
|
||
display_data = all_data[-50:] if len(all_data) > 50 else all_data
|
||
text = "压力-输出曲线数据点:\n"
|
||
for dp in display_data:
|
||
bar_len = int(abs(dp.output) * 10) if dp.output else 1
|
||
bar = "█" * min(bar_len, 40)
|
||
text += f"{dp.pressure:8.4f} MPa | {bar} {dp.output:.4f}\n"
|
||
self.plot_widget.setText(text)
|
||
|
||
def _refresh_status(self) -> None:
|
||
"""定时刷新系统状态"""
|
||
status = self._test_engine.system_status
|
||
if status.system_mode == SystemMode.NA:
|
||
self.status_indicator.setText("● 离线模式")
|
||
self.status_indicator.setStyleSheet("color: red; font-weight: bold;")
|
||
elif status.system_mode == SystemMode.NORMAL:
|
||
self.status_indicator.setText("● 系统正常")
|
||
self.status_indicator.setStyleSheet("color: green; font-weight: bold;")
|
||
else:
|
||
self.status_indicator.setText("● 维护模式")
|
||
self.status_indicator.setStyleSheet("color: orange; font-weight: bold;")
|
||
|
||
def _on_test_completed(self, data_points) -> None:
|
||
"""测试完成回调"""
|
||
self.statusBar().showMessage("测试完成,正在计算判定结果...")
|
||
|
||
# 停止采集线程
|
||
if self._acq_worker:
|
||
self._acq_worker.stop()
|
||
if self._acq_thread:
|
||
self._acq_thread.quit()
|
||
self._acq_thread.wait()
|
||
|
||
# 计算判定结果
|
||
config = self._sensor_config.get_config(
|
||
self._test_engine.context.current_model
|
||
)
|
||
if config:
|
||
self._last_judgment = self._calc_service.calculate(
|
||
data_points, config
|
||
)
|
||
self._display_judgment(self._last_judgment)
|
||
self.report_btn.setEnabled(True)
|
||
|
||
# 重置按钮
|
||
self.start_btn.setEnabled(True)
|
||
self.pause_btn.setEnabled(False)
|
||
self.stop_btn.setEnabled(False)
|
||
self.skip_btn.setEnabled(False)
|
||
self.load_btn.setEnabled(True)
|
||
self.pause_btn.setText("⏸ 暂停")
|
||
|
||
def _display_judgment(self, judgment: JudgmentResult) -> None:
|
||
"""
|
||
显示判定结果
|
||
|
||
Args:
|
||
judgment: 判定结果对象
|
||
"""
|
||
if judgment.result == TestResult.PASS:
|
||
self.result_label.setText("✓ PASS")
|
||
self.result_label.setStyleSheet(
|
||
"font-size: 24px; font-weight: bold; padding: 10px; "
|
||
"color: green; background-color: #e8f5e8;"
|
||
)
|
||
else:
|
||
self.result_label.setText("✗ FAIL")
|
||
self.result_label.setStyleSheet(
|
||
"font-size: 24px; font-weight: bold; padding: 10px; "
|
||
"color: red; background-color: #fde8e8;"
|
||
)
|
||
|
||
self.nonlinearity_label.setText(f"{judgment.non_linearity:.4f} %")
|
||
self.hysteresis_label.setText(f"{judgment.hysteresis:.4f} %")
|
||
self.repeatability_label.setText(f"{judgment.repeatability:.4f} %")
|
||
self.details_text.setText(judgment.details if judgment.details else "无异常")
|
||
|
||
def _generate_report(self) -> None:
|
||
"""生成测试报告"""
|
||
if not self._last_judgment:
|
||
QMessageBox.warning(self, "无测试数据", "请先完成一次测试。")
|
||
return
|
||
|
||
self.report_status.setText("正在生成报告...")
|
||
self.report_btn.setEnabled(False)
|
||
|
||
try:
|
||
# 生成报告
|
||
report = self._report_service.generate_report(
|
||
self._test_engine.context,
|
||
self._last_judgment,
|
||
self._current_user.user_id
|
||
)
|
||
|
||
# 生成PDF
|
||
pdf_data = self._report_service.generate_pdf_report(report)
|
||
|
||
# 生成CSV
|
||
csv_data = self._report_service.generate_csv_data(report)
|
||
|
||
self.report_status.setText(
|
||
f"报告已生成: {report.test_id}\n"
|
||
f"PDF: {len(pdf_data)} 字节 | CSV: {len(csv_data)} 字节\n"
|
||
f"SM3: {report.sm3_hash[:16]}..."
|
||
)
|
||
|
||
QMessageBox.information(
|
||
self, "报告生成成功",
|
||
f"测试报告已生成\n"
|
||
f"测试编号: {report.test_id}\n"
|
||
f"SM3哈希: {report.sm3_hash}\n"
|
||
f"数字水印已嵌入\n\n"
|
||
f"注:在实际系统中,报告将通过认证U盘导出。"
|
||
)
|
||
|
||
except Exception as e:
|
||
self.report_status.setText(f"报告生成失败: {str(e)}")
|
||
QMessageBox.critical(self, "报告生成失败", str(e))
|
||
finally:
|
||
self.report_btn.setEnabled(True)
|
||
|
||
def _do_logout(self) -> None:
|
||
"""注销当前用户"""
|
||
self._test_engine.shutdown()
|
||
self._security.logout()
|
||
self.close()
|
||
|
||
|
||
# =============================================================================
|
||
# 应用入口
|
||
# =============================================================================
|
||
|
||
def main():
|
||
"""
|
||
应用主入口函数
|
||
|
||
启动传感器测试设备软件,显示登录界面。
|
||
"""
|
||
app = QApplication(sys.argv)
|
||
app.setApplicationName("传感器测试设备软件")
|
||
app.setApplicationVersion("1.0.0")
|
||
|
||
# 设置全局样式
|
||
app.setStyle("Fusion")
|
||
palette = QPalette()
|
||
palette.setColor(QPalette.Window, QColor(240, 240, 240))
|
||
app.setPalette(palette)
|
||
|
||
# 初始化安全服务
|
||
security = SecurityService()
|
||
|
||
# 显示登录对话框
|
||
login = LoginDialog(security)
|
||
if login.exec_() == QDialog.Accepted:
|
||
user = security.get_current_user()
|
||
if user:
|
||
window = MainWindow(user)
|
||
window.show()
|
||
sys.exit(app.exec_())
|
||
else:
|
||
sys.exit(0)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|