forked from Rowland/EG
658 lines
24 KiB
Python
658 lines
24 KiB
Python
#!/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() |