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