EG/ui/maintenance_system.py

658 lines
24 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 -*-
"""
维修系统界面模块
包含登录界面、科目列表、模式选择等功能
"""
import os
import json
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QFormLayout, QGroupBox,
QLineEdit, QPushButton, QLabel, QListWidget, QListWidgetItem,
QMessageBox, QRadioButton, QTextEdit, QSplitter
)
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QFont, QPixmap
class MaintenanceLoginDialog(QDialog):
"""维修系统登录对话框"""
login_success = pyqtSignal() # 登录成功信号
def __init__(self, parent=None):
super().__init__(parent)
self.setupUI()
def setupUI(self):
"""设置用户界面"""
self.setWindowTitle("维修系统 - 登录")
self.setFixedSize(400, 300)
self.setModal(True)
# 应用简化样式 - 确保可见性和可用性
self.setStyleSheet("""
QDialog {
background-color: #f5f5f5;
color: #333333;
}
QLabel {
color: #333333;
font-size: 14px;
}
QLineEdit {
background-color: white;
color: #333333;
border: 2px solid #cccccc;
border-radius: 4px;
padding: 8px;
font-size: 14px;
min-height: 20px;
}
QLineEdit:focus {
border-color: #0078d4;
}
QPushButton {
background-color: #0078d4;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
min-height: 30px;
}
QPushButton:hover {
background-color: #106ebe;
}
QPushButton:pressed {
background-color: #005a9e;
}
QGroupBox {
font-size: 16px;
font-weight: bold;
color: #0078d4;
border: 2px solid #cccccc;
border-radius: 4px;
margin-top: 10px;
padding-top: 10px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px 0 5px;
}
""")
layout = QVBoxLayout(self)
layout.setSpacing(15)
layout.setContentsMargins(20, 20, 20, 20)
# 标题
title_label = QLabel("🔧 维修系统")
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet("""
font-size: 20px;
font-weight: bold;
color: #0078d4;
margin: 10px 0px;
""")
layout.addWidget(title_label)
# 登录表单
login_group = QGroupBox("身份验证")
login_layout = QFormLayout(login_group)
login_layout.setSpacing(10)
login_layout.setContentsMargins(15, 15, 15, 15)
# 用户名
self.username_edit = QLineEdit()
self.username_edit.setPlaceholderText("请输入用户名")
self.username_edit.setText("") # 确保清空
login_layout.addRow("用户名:", self.username_edit)
# 密码
self.password_edit = QLineEdit()
self.password_edit.setEchoMode(QLineEdit.Password)
self.password_edit.setPlaceholderText("请输入密码")
self.password_edit.setText("") # 确保清空
login_layout.addRow("密码:", self.password_edit)
layout.addWidget(login_group)
# 提示信息
hint_label = QLabel("💡 默认账号密码均为: admin")
hint_label.setAlignment(Qt.AlignCenter)
hint_label.setStyleSheet("""
color: #666666;
font-size: 12px;
margin: 5px 0px;
""")
layout.addWidget(hint_label)
# 按钮
button_layout = QHBoxLayout()
button_layout.setSpacing(10)
self.cancel_btn = QPushButton("取消")
self.login_btn = QPushButton("登录")
button_layout.addStretch()
button_layout.addWidget(self.cancel_btn)
button_layout.addWidget(self.login_btn)
layout.addLayout(button_layout)
# 连接信号
self.login_btn.clicked.connect(self.on_login)
self.cancel_btn.clicked.connect(self.reject)
self.password_edit.returnPressed.connect(self.on_login)
# 设置默认焦点和tab顺序
self.username_edit.setFocus()
self.setTabOrder(self.username_edit, self.password_edit)
self.setTabOrder(self.password_edit, self.login_btn)
self.setTabOrder(self.login_btn, self.cancel_btn)
print("🔧 登录界面创建完成")
print(f"📝 用户名输入框: {self.username_edit}")
print(f"📝 密码输入框: {self.password_edit}")
print(f"📝 登录按钮: {self.login_btn}")
print(f"📝 取消按钮: {self.cancel_btn}")
def on_login(self):
"""处理登录"""
username = self.username_edit.text().strip()
password = self.password_edit.text().strip()
# 验证账号密码
if username == "admin" and password == "admin":
print("✅ 维修系统登录成功")
self.login_success.emit()
self.accept()
else:
QMessageBox.warning(self, "登录失败",
"用户名或密码错误!\n请使用: admin / admin")
self.password_edit.clear()
self.password_edit.setFocus()
class MaintenanceSubjectDialog(QDialog):
"""维修系统科目选择对话框"""
subject_selected = pyqtSignal(str, str) # 科目路径, 模式
def __init__(self, project_path, parent=None):
super().__init__(parent)
self.project_path = project_path
self.subjects_path = os.path.join(project_path, "Subjects") if project_path else None
self.current_subject_path = None
self.setupUI()
self.load_subjects()
def setupUI(self):
"""设置用户界面"""
self.setWindowTitle("维修系统 - 科目选择")
self.setFixedSize(800, 600)
self.setModal(True)
# 应用样式
self.setStyleSheet("""
QDialog {
background-color: #1e1e2e;
color: #e0e0ff;
}
QLabel {
color: #e0e0ff;
}
QListWidget {
background-color: #2d2d44;
color: #e0e0ff;
border: 2px solid #3a3a4a;
border-radius: 8px;
padding: 5px;
font-size: 14px;
}
QListWidget::item {
padding: 8px;
border-radius: 4px;
margin: 2px;
}
QListWidget::item:hover {
background-color: #3a3a4a;
}
QListWidget::item:selected {
background-color: #8b5cf6;
color: white;
}
QTextEdit {
background-color: #2d2d44;
color: #e0e0ff;
border: 2px solid #3a3a4a;
border-radius: 8px;
padding: 8px;
font-size: 12px;
}
QPushButton {
background-color: #8b5cf6;
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-weight: bold;
font-size: 14px;
}
QPushButton:hover {
background-color: #7c3aed;
}
QPushButton:pressed {
background-color: #6d28d9;
}
QPushButton:disabled {
background-color: #4c4c6e;
color: #8888aa;
}
QGroupBox {
font-size: 14px;
font-weight: bold;
color: #8b5cf6;
border: 2px solid #3a3a4a;
border-radius: 8px;
margin: 10px 0px;
padding-top: 15px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px 0 5px;
}
QRadioButton {
color: #e0e0ff;
font-size: 14px;
spacing: 8px;
}
QRadioButton::indicator {
width: 16px;
height: 16px;
}
QRadioButton::indicator:unchecked {
border: 2px solid #3a3a4a;
border-radius: 8px;
background-color: #2d2d44;
}
QRadioButton::indicator:checked {
border: 2px solid #8b5cf6;
border-radius: 8px;
background-color: #8b5cf6;
}
""")
layout = QVBoxLayout(self)
# 标题
title_label = QLabel("📚 选择维修科目")
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet("""
font-size: 20px;
font-weight: bold;
color: #8b5cf6;
margin: 15px 0px;
""")
layout.addWidget(title_label)
# 主要内容区域
main_splitter = QSplitter(Qt.Horizontal)
# 左侧:科目列表
left_widget = QGroupBox("科目列表")
left_layout = QVBoxLayout(left_widget)
self.subject_list = QListWidget()
self.subject_list.itemClicked.connect(self.on_subject_selected)
left_layout.addWidget(self.subject_list)
main_splitter.addWidget(left_widget)
# 右侧:科目详情和模式选择
right_widget = QGroupBox("科目详情")
right_layout = QVBoxLayout(right_widget)
# 科目描述
self.subject_description = QTextEdit()
self.subject_description.setReadOnly(True)
self.subject_description.setMaximumHeight(150)
self.subject_description.setPlainText("请选择一个科目查看详情...")
right_layout.addWidget(self.subject_description)
# 模式选择
mode_group = QGroupBox("选择模式")
mode_layout = QVBoxLayout(mode_group)
self.training_radio = QRadioButton("🎓 训练模式")
self.training_radio.setChecked(True)
mode_layout.addWidget(self.training_radio)
training_desc = QLabel("• 显示详细的步骤描述和操作提示\n• 提供工具选择的正确性提示\n• 播放语音指导")
training_desc.setStyleSheet("color: #94a3b8; font-size: 12px; margin-left: 20px;")
mode_layout.addWidget(training_desc)
self.exam_radio = QRadioButton("📋 考核模式")
mode_layout.addWidget(self.exam_radio)
exam_desc = QLabel("• 不显示步骤描述\n• 不提供工具选择提示\n• 无语音指导,独立完成操作")
exam_desc.setStyleSheet("color: #94a3b8; font-size: 12px; margin-left: 20px;")
mode_layout.addWidget(exam_desc)
right_layout.addWidget(mode_group)
main_splitter.addWidget(right_widget)
# 设置分割器比例
main_splitter.setSizes([300, 500])
layout.addWidget(main_splitter)
# 底部按钮
button_layout = QHBoxLayout()
self.refresh_btn = QPushButton("🔄 刷新")
self.cancel_btn = QPushButton("取消")
self.start_btn = QPushButton("开始")
self.start_btn.setEnabled(False)
button_layout.addWidget(self.refresh_btn)
button_layout.addStretch()
button_layout.addWidget(self.cancel_btn)
button_layout.addWidget(self.start_btn)
layout.addLayout(button_layout)
# 连接信号
self.refresh_btn.clicked.connect(self.load_subjects)
self.cancel_btn.clicked.connect(self.reject)
self.start_btn.clicked.connect(self.on_start)
def load_subjects(self):
"""加载科目列表"""
self.subject_list.clear()
self.current_subject_path = None
self.start_btn.setEnabled(False)
self.subject_description.setPlainText("请选择一个科目查看详情...")
if not self.subjects_path or not os.path.exists(self.subjects_path):
# 添加提示项
item = QListWidgetItem("📁 未找到Subjects目录")
item.setData(Qt.UserRole, None)
self.subject_list.addItem(item)
if not self.project_path:
self.subject_description.setPlainText(
"❌ 错误:没有打开的项目\n\n"
"请先通过 文件 -> 打开 菜单打开一个项目,\n"
"然后再使用维修系统功能。"
)
else:
self.subject_description.setPlainText(
f"❌ 错误找不到Subjects目录\n\n"
f"项目路径: {self.project_path}\n"
f"期望目录: {self.subjects_path}\n\n"
"请在项目根目录下创建Subjects文件夹\n"
"并在其中放置科目的JSON配置文件。"
)
return
# 扫描JSON文件
json_files = []
try:
for file in os.listdir(self.subjects_path):
if file.endswith('.json'):
json_path = os.path.join(self.subjects_path, file)
json_files.append((file, json_path))
except Exception as e:
print(f"❌ 扫描Subjects目录失败: {e}")
item = QListWidgetItem("❌ 读取目录失败")
item.setData(Qt.UserRole, None)
self.subject_list.addItem(item)
return
if not json_files:
item = QListWidgetItem("📄 目录中没有JSON文件")
item.setData(Qt.UserRole, None)
self.subject_list.addItem(item)
self.subject_description.setPlainText(
f" 提示Subjects目录为空\n\n"
f"目录路径: {self.subjects_path}\n\n"
"请在此目录中放置维修科目的JSON配置文件。"
)
return
# 加载科目
loaded_count = 0
for filename, filepath in json_files:
try:
with open(filepath, 'r', encoding='utf-8') as f:
subject_data = json.load(f)
# 获取科目名称
subject_name = subject_data.get('name', filename.replace('.json', ''))
# 创建列表项
item = QListWidgetItem(f"📋 {subject_name}")
item.setData(Qt.UserRole, {
'path': filepath,
'data': subject_data,
'filename': filename
})
self.subject_list.addItem(item)
loaded_count += 1
except Exception as e:
print(f"❌ 加载科目文件失败 {filename}: {e}")
item = QListWidgetItem(f"{filename} (加载失败)")
item.setData(Qt.UserRole, None)
self.subject_list.addItem(item)
print(f"✅ 成功加载 {loaded_count} 个维修科目")
def on_subject_selected(self, item):
"""处理科目选择"""
try:
print(f"📋 科目选择事件触发item: {item}")
if not item:
print("❌ item为空")
self.current_subject_path = None
self.start_btn.setEnabled(False)
self.subject_description.setPlainText("请选择一个有效的科目。")
return
subject_info = item.data(Qt.UserRole)
print(f"📋 科目信息: {subject_info}")
if not subject_info:
print("❌ 科目信息为空")
self.current_subject_path = None
self.start_btn.setEnabled(False)
self.subject_description.setPlainText("此科目无法加载,请检查文件格式。")
return
self.current_subject_path = subject_info['path']
self.start_btn.setEnabled(True)
print(f"✅ 选择科目: {subject_info.get('filename', 'unknown')}")
# 显示科目详情
subject_data = subject_info['data']
description = self.format_subject_description(subject_data, subject_info['filename'])
self.subject_description.setPlainText(description)
print("✅ 科目详情显示完成")
except Exception as e:
print(f"❌ 科目选择处理失败: {e}")
import traceback
traceback.print_exc()
self.current_subject_path = None
self.start_btn.setEnabled(False)
self.subject_description.setPlainText(f"处理科目信息时出错:\n{str(e)}")
# 显示错误对话框
QMessageBox.critical(self, "错误", f"处理科目选择时出错:\n{str(e)}")
def format_subject_description(self, subject_data, filename):
"""格式化科目描述"""
try:
print(f"🔧 格式化科目描述: {filename}")
if not subject_data:
return "❌ 科目数据为空"
lines = []
# 基本信息
name = subject_data.get('name', '未知科目')
lines.append(f"📋 科目: {name}")
lines.append(f"📁 文件: {filename}")
lines.append("")
# 描述信息
if 'description' in subject_data and subject_data['description']:
lines.append(f"📝 描述: {subject_data['description']}")
lines.append("")
# 步骤信息
steps = subject_data.get('steps', [])
if steps and isinstance(steps, list):
lines.append(f"📊 步骤数量: {len(steps)}")
# 计算总分
try:
total_score = 0
for step in steps:
if isinstance(step, dict):
score = step.get('score', 0)
if isinstance(score, (int, float)):
total_score += score
if total_score > 0:
lines.append(f"💯 总分: {total_score}")
except Exception as e:
print(f"⚠️ 计算总分时出错: {e}")
lines.append("")
lines.append("📋 步骤列表:")
for i, step in enumerate(steps, 1):
try:
if isinstance(step, dict):
step_name = step.get('name', f'步骤{i}')
step_score = step.get('score', 0)
if isinstance(step_score, (int, float)) and step_score > 0:
lines.append(f" {i}. {step_name} ({step_score}分)")
else:
lines.append(f" {i}. {step_name}")
else:
lines.append(f" {i}. 步骤{i} (格式错误)")
except Exception as e:
print(f"⚠️ 处理步骤{i}时出错: {e}")
lines.append(f" {i}. 步骤{i} (处理错误)")
else:
lines.append("⚠️ 此科目没有配置步骤")
# 工具信息
tools = subject_data.get('tools', [])
if tools and isinstance(tools, list):
try:
valid_tools = [str(tool) for tool in tools if tool]
if valid_tools:
lines.append("")
lines.append(f"🔧 可用工具: {', '.join(valid_tools)}")
except Exception as e:
print(f"⚠️ 处理工具列表时出错: {e}")
lines.append("")
lines.append("🔧 可用工具: (处理错误)")
result = '\n'.join(lines)
print(f"✅ 科目描述格式化完成,长度: {len(result)}")
return result
except Exception as e:
print(f"❌ 格式化科目描述失败: {e}")
import traceback
traceback.print_exc()
return f"❌ 格式化科目描述时出错:\n{str(e)}\n\n文件: {filename}"
def on_start(self):
"""开始科目"""
if not self.current_subject_path:
QMessageBox.warning(self, "错误", "请先选择一个科目!")
return
# 获取选择的模式
mode = "training" if self.training_radio.isChecked() else "exam"
print(f"🚀 开始维修科目: {self.current_subject_path}, 模式: {mode}")
# 发射信号
self.subject_selected.emit(self.current_subject_path, mode)
self.accept()
class MaintenanceSystemManager:
"""维修系统管理器"""
def __init__(self, world):
self.world = world
self.login_dialog = None
self.subject_dialog = None
def show_maintenance_system(self, parent_window):
"""显示维修系统"""
# 创建登录对话框
self.login_dialog = MaintenanceLoginDialog(parent_window)
self.login_dialog.login_success.connect(
lambda: self.show_subject_selection(parent_window)
)
self.login_dialog.exec_()
def show_subject_selection(self, parent_window):
"""显示科目选择界面"""
# 获取当前项目路径
project_path = None
if hasattr(self.world, 'project_manager') and self.world.project_manager:
project_path = self.world.project_manager.getCurrentProjectPath()
# 创建科目选择对话框
self.subject_dialog = MaintenanceSubjectDialog(project_path, parent_window)
self.subject_dialog.subject_selected.connect(self.start_maintenance_subject)
self.subject_dialog.exec_()
def start_maintenance_subject(self, subject_path, mode):
"""开始维修科目"""
try:
print(f"🎯 启动维修科目: {subject_path}")
print(f"📝 模式: {mode}")
# 加载科目配置
with open(subject_path, 'r', encoding='utf-8') as f:
subject_config = json.load(f)
# 启动拆装交互系统
if hasattr(self.world, 'assembly_interaction') and self.world.assembly_interaction:
# 设置配置
self.world.assembly_interaction.config_data = subject_config
# 启动交互模式
self.world.assembly_interaction.start_interaction_mode(mode=mode)
print(f"✅ 维修科目启动成功")
else:
print("❌ 错误:拆装交互系统未初始化")
except Exception as e:
print(f"❌ 启动维修科目失败: {e}")
import traceback
traceback.print_exc()