当前位置: 首页 > news >正文

【完整源码】白泽题库系统:基于PyQt5的智能刷题与考试平台开发全解析

🎯 【完整源码】白泽题库系统:基于PyQt5的智能刷题与考试平台开发全解析

在这里插入图片描述
请添加图片描述

🌈 个人主页:创客白泽 - CSDN博客
🔥 系列专栏:🐍《Python开源项目实战》
💡 热爱不止于代码,热情源自每一个灵感闪现的夜晚。愿以开源之火,点亮前行之路。
🐋 希望大家多多支持,我们一起进步!
👍 🎉如果文章对你有帮助的话,欢迎 点赞 👍🏻 评论 💬 收藏 ⭐️ 加关注+💗分享给更多人哦

请添加图片描述

📌 概述

在当今信息化教育时代,在线学习与考试系统已成为教育技术领域的重要组成部分。本文将详细介绍一款名为"白泽题库系统"的智能刷题软件的设计与实现,该系统基于Python的PyQt5框架开发,集题库管理、随机组卷、智能刷题、错题集和学习统计等功能于一体。

白泽是中国古代神话中的神兽,以博学多识著称,本系统取其"智慧"之意,旨在为用户提供一个高效、智能的刷题学习平台。系统采用模块化设计,具有良好的扩展性和用户体验,特别适合各类考试备考使用。

在这里插入图片描述

🛠️ 功能特点

白泽题库系统具备以下核心功能模块:

功能模块描述技术实现
📂 题库管理支持创建、编辑、导入/导出题库JSON/Excel文件处理
📋 随机组卷按题型比例随机生成试卷随机算法、计时功能
🌊 题海刷题顺序/逆序/随机刷题模式用户答案记录
❌ 错题集自动收集错题,支持复习数据结构存储
📈 学习统计可视化错题分布与趋势Matplotlib图表

系统采用MVC架构设计,主要技术栈包括:

  • 前端:PyQt5 + Qt Designer
  • 数据处理:JSON + openpyxl
  • 图表展示:Matplotlib
  • 核心逻辑:Python 3.x

🎨 界面展示

主界面

https://via.placeholder.com/600x400?text=Main+Page

题库管理

在这里插入图片描述
在这里插入图片描述

考试模式

在这里插入图片描述
在这里插入图片描述

刷题模式

在这里插入图片描述

错题集

在这里插入图片描述

学习统计

在这里插入图片描述

🧩 系统架构设计

类结构图

在这里插入图片描述

Excel题库填写格式模板

在这里插入图片描述

题库题目填写格式表

字段名类型必填说明示例
type字符串题目类型,可选值:“单选题”、“多选题”、“判断题”“单选题”
question字符串题目内容“Python是什么类型的语言?”
options字符串列表单选/多选必填题目选项(判断题固定为[“正确”, “错误”])[“编译型”, “解释型”, “混合型”, “汇编型”]
answer字符串正确答案(多选题用逗号+空格分隔)“B”(单选) “A, B, C”(多选) “正确”(判断)
score整数题目分值(默认1分)2
explanation字符串题目解析(可选)“Python是解释型语言,代码在运行时逐行解释执行。”

核心数据结构

  1. 题库存储结构
{"题库名称": [{"type": "单选题","question": "问题文本","options": ["A", "B", "C", "D"],"answer": "A","score": 2},# 更多题目...]
}
  1. 错题集结构
{"题库名称": [{"type": "单选题","question": "问题文本","options": ["A", "B", "C", "D"],"answer": "A","user_answer": "B","score": 2},# 更多错题...]
}

🛠️ 实现步骤详解

1. 环境配置

首先需要安装必要的Python库:

pip install pyqt5 openpyxl matplotlib

2. 主窗口初始化

class BrainyQuiz(QMainWindow):def __init__(self):super().__init__()self.setWindowTitle("白泽题库系统")self.setGeometry(100, 100, 1000, 700)self.setStyleSheet(self.get_stylesheet())# 初始化数据self.current_question_bank = Noneself.question_banks = {}self.current_questions = []self.current_index = 0self.user_answers = {}self.wrong_questions = {}# 创建界面self.init_ui()self.load_question_bank_list()

3. 界面布局设计

系统采用侧边栏导航+堆叠窗口的设计模式:

def init_ui(self):self.central_widget = QWidget()self.setCentralWidget(self.central_widget)# 主布局:水平布局(侧边栏+内容区)self.main_layout = QHBoxLayout(self.central_widget)# 创建侧边栏self.create_sidebar()# 创建内容区域(堆叠窗口)self.content_area = QStackedWidget()self.create_all_pages()# 将组件添加到主布局self.main_layout.addWidget(self.sidebar)self.main_layout.addWidget(self.content_area)

4. 题库管理实现

题库支持JSON和Excel两种格式的导入导出:

def import_question_bank(self):filename, _ = QFileDialog.getOpenFileName(self, "导入题库", "", "题库文件 (*.json *.xlsx)")if filename.endswith('.json'):with open(filename, 'r', encoding='utf-8') as f:bank_name = filename.split('/')[-1].replace('.json', '')self.question_banks[bank_name] = json.load(f)elif filename.endswith('.xlsx'):bank_name = filename.split('/')[-1].replace('.xlsx', '')self.question_banks[bank_name] = self.load_excel_bank(filename)

5. 随机组卷算法

按题型比例随机抽取题目:

def start_exam(self):# 计算各题型题目数量single_count = int(num_questions * single_ratio / 100)multi_count = int(num_questions * multi_ratio / 100)bool_count = num_questions - single_count - multi_count# 随机选择题目selected_questions = []if single_count > 0:selected_questions.extend(random.sample(single_questions, single_count))if multi_count > 0:selected_questions.extend(random.sample(multi_questions, multi_count))if bool_count > 0:selected_questions.extend(random.sample(bool_questions, bool_count))# 随机排序random.shuffle(selected_questions)

6. 刷题模式实现

支持三种刷题顺序:

def start_practice(self):self.practice_order = self.order_combo.currentText()self.current_questions = questions.copy()if self.practice_order == "逆序":self.current_questions.reverse()elif self.practice_order == "随机":random.shuffle(self.current_questions)

7. 错题集管理

自动记录错题并提供复习功能:

def submit_exam(self):if not is_correct:# 添加到错题集if self.current_question_bank not in self.wrong_questions:self.wrong_questions[self.current_question_bank] = []wrong_question = question.copy()wrong_question['user_answer'] = user_answerself.wrong_questions[self.current_question_bank].append(wrong_question)

8. 学习统计可视化

使用Matplotlib生成错题分析图表:

def show_stats(self):# 创建图表fig = self.stats_canvas.figurefig.clear()# 错题题型分布饼图ax1 = fig.add_subplot(121)ax1.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90)# 各题库错题数量柱状图ax2 = fig.add_subplot(122)ax2.bar(bank_names, wrong_counts)self.stats_canvas.draw()

🔍 关键代码解析

1. 题目显示逻辑

def show_question(self):# 获取当前题目current_question = self.current_questions[self.current_index]# 显示题目信息self.question_text.setText(current_question['question'])# 根据题型创建相应选项if current_question['type'] == "单选题":for i, option in enumerate(current_question['options']):rb = QRadioButton(f"{chr(65+i)}. {option}")self.options_layout.addWidget(rb)

2. 答案检查逻辑

def check_answer(self, user_answer, correct_answer, q_type):if q_type == "多选题":# 对多选题答案进行排序比较user_sorted = "".join(sorted(user_answer.upper()))correct_sorted = "".join(sorted(correct_answer.replace(", ", "").upper()))return user_sorted == correct_sortedelse:return str(user_answer).upper() == str(correct_answer).upper()

3. 样式表设计

def get_stylesheet(self):return """QMainWindow { background-color: #f5f7fa; }QLabel { color: #333; }QPushButton {background-color: #4e73df;color: white;border: none;padding: 8px 16px;border-radius: 4px;}QPushButton:hover { background-color: #3a5fcd; }"""

📥 源码下载

import sys
import json
import openpyxl
import random
from PyQt5 import QtGui
from PyQt5.QtGui import QIntValidator
from datetime import datetime
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QListWidget, QStackedWidget, QMessageBox,QLineEdit, QTextEdit, QRadioButton, QCheckBox, QComboBox,QGroupBox, QDialog, QFormLayout, QInputDialog, QFileDialog,QTreeWidget, QTreeWidgetItem, QAbstractItemView, QTabWidget)
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QFont, QIcon, QPixmap
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvasclass BrainyQuiz(QMainWindow):def __init__(self):super().__init__()self.setWindowTitle("白泽题库系统")self.setGeometry(100, 100, 1000, 700)self.setStyleSheet(self.get_stylesheet())# 初始化数据self.current_question_bank = Noneself.question_banks = {}self.current_questions = []self.current_index = 0self.user_answers = {}self.wrong_questions = {}self.exam_mode = Falseself.practice_mode = Falseself.score = 0self.total_score = 0self.practice_order = "顺序"# 创建主界面self.init_ui()# 加载题库列表self.load_question_bank_list()def get_stylesheet(self):"""返回应用程序样式表"""return """QMainWindow {background-color: #f5f7fa;}QLabel {color: #333;}QPushButton {background-color: #4e73df;color: white;border: none;padding: 8px 16px;border-radius: 4px;min-width: 100px;}QPushButton:hover {background-color: #3a5fcd;}QPushButton:disabled {background-color: #cccccc;}QListWidget, QTreeWidget, QTextEdit, QLineEdit, QComboBox {border: 1px solid #ddd;border-radius: 4px;padding: 5px;background-color: white;}QGroupBox {border: 1px solid #ddd;border-radius: 4px;margin-top: 10px;padding-top: 15px;}QGroupBox::title {subcontrol-origin: margin;left: 10px;}QRadioButton, QCheckBox {spacing: 5px;}"""def init_ui(self):"""初始化UI界面"""self.central_widget = QWidget()self.setCentralWidget(self.central_widget)self.main_layout = QHBoxLayout(self.central_widget)# 左侧导航栏self.create_sidebar()# 右侧内容区域self.content_area = QStackedWidget()# 添加各个页面self.create_all_pages()# 将侧边栏和内容区域添加到主布局self.main_layout.addWidget(self.sidebar)self.main_layout.addWidget(self.content_area)def create_sidebar(self):"""创建左侧导航栏"""self.sidebar = QWidget()self.sidebar.setFixedWidth(200)self.sidebar.setStyleSheet("background-color: #2c3e50;")self.sidebar_layout = QVBoxLayout(self.sidebar)# 应用标题title = QLabel("🎓 题库考试系统")title.setStyleSheet("""QLabel {color: white;font-size: 18px;font-weight: bold;padding: 20px 10px;}""")title.setAlignment(Qt.AlignCenter)self.sidebar_layout.addWidget(title)# 导航按钮self.create_navigation_buttons()# 添加按钮到侧边栏self.sidebar_layout.addStretch()self.sidebar_layout.addWidget(self.btn_exit)def create_navigation_buttons(self):"""创建导航按钮"""self.btn_bank = QPushButton("📂 题库管理")self.btn_bank.setStyleSheet("text-align: left; padding-left: 20px;")self.btn_bank.clicked.connect(self.show_bank_manager)self.btn_exam = QPushButton("📋 随机组卷")self.btn_exam.setStyleSheet("text-align: left; padding-left: 20px;")self.btn_exam.clicked.connect(self.show_exam_setup)self.btn_practice = QPushButton("🌊 题海刷题")self.btn_practice.setStyleSheet("text-align: left; padding-left: 20px;")self.btn_practice.clicked.connect(self.show_practice_setup)self.btn_wrong = QPushButton("❌ 错题集")self.btn_wrong.setStyleSheet("text-align: left; padding-left: 20px;")self.btn_wrong.clicked.connect(self.show_wrong_questions)self.btn_stats = QPushButton("📈 学习统计")self.btn_stats.setStyleSheet("text-align: left; padding-left: 20px;")self.btn_stats.clicked.connect(self.show_stats)self.btn_exit = QPushButton("🚪 退出")self.btn_exit.setStyleSheet("text-align: left; padding-left: 20px;")self.btn_exit.clicked.connect(self.close)# 添加按钮到侧边栏buttons = [self.btn_bank, self.btn_exam, self.btn_practice,self.btn_wrong, self.btn_stats]for btn in buttons:self.sidebar_layout.addWidget(btn)def create_all_pages(self):"""创建所有内容页面"""self.main_page = self.create_main_page()self.bank_manager_page = self.create_bank_manager_page()self.bank_editor_page = self.create_bank_editor_page()self.exam_setup_page = self.create_exam_setup_page()self.exam_page = self.create_exam_page()self.result_page = self.create_result_page()self.wrong_questions_page = self.create_wrong_questions_page()self.stats_page = self.create_stats_page()self.practice_setup_page = self.create_practice_setup_page()self.practice_page = self.create_practice_page()# 添加到堆栈窗口pages = [self.main_page, self.bank_manager_page, self.bank_editor_page,self.exam_setup_page, self.exam_page, self.result_page,self.wrong_questions_page, self.stats_page,self.practice_setup_page, self.practice_page]for page in pages:self.content_area.addWidget(page)def create_main_page(self):"""创建主页面"""page = QWidget()layout = QVBoxLayout(page)# 标题title = QLabel("白泽为引,点亮你的题海修行之路")title.setStyleSheet("""QLabel {font-size: 24px;font-weight: bold;color: #2c3e50;margin-bottom: 30px;}""")title.setAlignment(Qt.AlignCenter)layout.addWidget(title)# 应用描述desc = QLabel("一款功能强大的刷题软件,支持多种题型和智能学习统计")desc.setStyleSheet("font-size: 16px; color: #7f8c8d;")desc.setAlignment(Qt.AlignCenter)layout.addWidget(desc)# 插入空行spacer = QLabel("")spacer.setFixedHeight(15)  # 设置高度,10~20px 之间都可以layout.addWidget(spacer)# 图标(如果没有资源文件则不显示)icon = QLabel()try:pixmap = QPixmap("icon.ico")if not pixmap.isNull():pixmap = pixmap.scaled(100, 100, Qt.KeepAspectRatio, Qt.SmoothTransformation)icon.setPixmap(pixmap)icon.setAlignment(Qt.AlignCenter)layout.addWidget(icon)except:passlayout.addStretch()return pagedef create_bank_manager_page(self):"""创建题库管理页面"""page = QWidget()layout = QVBoxLayout(page)# 标题title = QLabel("📂 题库管理")title.setStyleSheet("""QLabel {font-size: 20px;font-weight: bold;margin-bottom: 20px;}""")title.setAlignment(Qt.AlignCenter)layout.addWidget(title)# 题库列表self.bank_list = QListWidget()self.bank_list.setStyleSheet("font-size: 14px;")layout.addWidget(self.bank_list)# 按钮区域btn_layout = QHBoxLayout()self.btn_new_bank = QPushButton("➕ 新建题库")self.btn_new_bank.clicked.connect(self.create_new_bank)self.btn_import_bank = QPushButton("📥 导入题库")self.btn_import_bank.clicked.connect(self.import_question_bank)self.btn_edit_bank = QPushButton("✏️ 编辑题库")self.btn_edit_bank.clicked.connect(self.edit_selected_bank)self.btn_export_bank = QPushButton("📤 导出题库")self.btn_export_bank.clicked.connect(self.export_selected_bank)self.btn_delete_bank = QPushButton("🗑️ 删除题库")self.btn_delete_bank.clicked.connect(self.delete_selected_bank)buttons = [self.btn_new_bank, self.btn_import_bank, self.btn_edit_bank,self.btn_export_bank, self.btn_delete_bank]for btn in buttons:btn_layout.addWidget(btn)layout.addLayout(btn_layout)# 返回按钮self.btn_back = QPushButton("🔙 返回")self.btn_back.clicked.connect(self.show_main_page)layout.addWidget(self.btn_back, alignment=Qt.AlignRight)return pagedef create_bank_editor_page(self):"""创建题库编辑器页面"""page = QWidget()layout = QVBoxLayout(page)# 标题self.bank_editor_title = QLabel()self.bank_editor_title.setStyleSheet("""QLabel {font-size: 20px;font-weight: bold;margin-bottom: 20px;}""")self.bank_editor_title.setAlignment(Qt.AlignCenter)layout.addWidget(self.bank_editor_title)# 题目列表self.question_tree = QTreeWidget()self.question_tree.setHeaderLabels(["题型", "题目", "选项", "答案", "分值"])self.question_tree.setColumnWidth(0, 80)self.question_tree.setColumnWidth(1, 300)self.question_tree.setColumnWidth(2, 200)self.question_tree.setColumnWidth(3, 100)self.question_tree.setColumnWidth(4, 60)self.question_tree.setSelectionMode(QAbstractItemView.SingleSelection)layout.addWidget(self.question_tree)# 按钮区域btn_layout = QHBoxLayout()self.btn_add_question = QPushButton("➕ 添加题目")self.btn_add_question.clicked.connect(self.add_question_dialog)self.btn_edit_question = QPushButton("✏️ 编辑题目")self.btn_edit_question.clicked.connect(self.edit_question_dialog)self.btn_delete_question = QPushButton("🗑️ 删除题目")self.btn_delete_question.clicked.connect(self.delete_question)buttons = [self.btn_add_question, self.btn_edit_question, self.btn_delete_question]for btn in buttons:btn_layout.addWidget(btn)layout.addLayout(btn_layout)# 保存和返回按钮bottom_btn_layout = QHBoxLayout()self.btn_save_bank = QPushButton("💾 保存题库")self.btn_save_bank.clicked.connect(self.save_question_bank)self.btn_back_editor = QPushButton("🔙 返回")self.btn_back_editor.clicked.connect(self.show_bank_manager)bottom_btn_layout.addWidget(self.btn_save_bank)bottom_btn_layout.addStretch()bottom_btn_layout.addWidget(self.btn_back_editor)layout.addLayout(bottom_btn_layout)return pagedef create_exam_setup_page(self):"""创建组卷设置页面"""page = QWidget()layout = QVBoxLayout(page)# 标题self.exam_setup_title = QLabel("📋 随机组卷设置")self.exam_setup_title.setStyleSheet("""QLabel {font-size: 20px;font-weight: bold;margin-bottom: 20px;}""")self.exam_setup_title.setAlignment(Qt.AlignCenter)layout.addWidget(self.exam_setup_title)# 设置表单form_layout = QFormLayout()# 题库选择self.bank_combo = QComboBox()form_layout.addRow("选择题库:", self.bank_combo)# 组卷设置self.exam_settings_group = QGroupBox("组卷设置")exam_layout = QVBoxLayout(self.exam_settings_group)# 题目数量self.num_questions = QLineEdit("10")self.num_questions.setValidator(QtGui.QIntValidator(1, 100))exam_layout.addWidget(QLabel("题目数量:"))exam_layout.addWidget(self.num_questions)# 题型分布type_layout = QHBoxLayout()self.single_ratio = QLineEdit("40")self.single_ratio.setValidator(QtGui.QIntValidator(0, 100))type_layout.addWidget(QLabel("单选:"))type_layout.addWidget(self.single_ratio)type_layout.addWidget(QLabel("%"))self.multi_ratio = QLineEdit("30")self.multi_ratio.setValidator(QtGui.QIntValidator(0, 100))type_layout.addWidget(QLabel("多选:"))type_layout.addWidget(self.multi_ratio)type_layout.addWidget(QLabel("%"))self.bool_ratio = QLineEdit("30")self.bool_ratio.setValidator(QtGui.QIntValidator(0, 100))type_layout.addWidget(QLabel("判断:"))type_layout.addWidget(self.bool_ratio)type_layout.addWidget(QLabel("%"))exam_layout.addWidget(QLabel("题型分布:"))exam_layout.addLayout(type_layout)# 时间限制self.time_limit_label = QLabel("时间限制(分钟):")self.time_limit = QLineEdit("30")self.time_limit.setValidator(QtGui.QIntValidator(1, 300))exam_layout.addWidget(self.time_limit_label)exam_layout.addWidget(self.time_limit)form_layout.addRow(self.exam_settings_group)layout.addLayout(form_layout)# 按钮区域btn_layout = QHBoxLayout()self.btn_start_exam = QPushButton("📝 开始考试")self.btn_start_exam.clicked.connect(self.start_exam)self.btn_back_setup = QPushButton("🔙 返回")self.btn_back_setup.clicked.connect(self.show_main_page)btn_layout.addWidget(self.btn_start_exam)btn_layout.addWidget(self.btn_back_setup)layout.addLayout(btn_layout)return pagedef create_exam_page(self):"""创建考试页面"""page = QWidget()layout = QVBoxLayout(page)# 计时器self.timer_layout = QHBoxLayout()self.timer_label = QLabel()self.timer_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #e74c3c;")self.timer_layout.addStretch()self.timer_layout.addWidget(self.timer_label)layout.addLayout(self.timer_layout)# 进度显示self.progress_label = QLabel()self.progress_label.setStyleSheet("font-size: 16px; font-weight: bold;")layout.addWidget(self.progress_label, alignment=Qt.AlignLeft)# 题目内容self.question_group = QGroupBox()self.question_group.setStyleSheet("QGroupBox { font-size: 14px; }")question_layout = QVBoxLayout(self.question_group)self.question_type_label = QLabel()self.question_type_label.setStyleSheet("font-weight: bold; color: #3498db;")self.question_text = QLabel()self.question_text.setWordWrap(True)self.question_text.setStyleSheet("font-size: 15px;")self.options_layout = QVBoxLayout()question_layout.addWidget(self.question_type_label)question_layout.addWidget(self.question_text)question_layout.addLayout(self.options_layout)question_layout.addStretch()layout.addWidget(self.question_group)# 按钮区域btn_layout = QHBoxLayout()self.btn_prev = QPushButton("⬅️ 上一题")self.btn_prev.clicked.connect(self.prev_question)self.btn_next = QPushButton("➡️ 下一题")self.btn_next.clicked.connect(self.next_question)self.btn_submit = QPushButton("📝 交卷")self.btn_submit.clicked.connect(self.submit_exam)self.btn_back_exam = QPushButton("🔙 返回")self.btn_back_exam.clicked.connect(self.confirm_exit_exam)buttons = [self.btn_prev, self.btn_next, self.btn_submit, self.btn_back_exam]for btn in buttons:btn_layout.addWidget(btn)layout.addLayout(btn_layout)return pagedef create_result_page(self):"""创建测验结果页面"""page = QWidget()layout = QVBoxLayout(page)# 标题title = QLabel("📊 测验结果")title.setStyleSheet("""QLabel {font-size: 20px;font-weight: bold;margin-bottom: 20px;}""")title.setAlignment(Qt.AlignCenter)layout.addWidget(title)# 结果统计self.stats_group = QGroupBox("测验统计")stats_layout = QFormLayout(self.stats_group)self.total_score_label = QLabel()self.accuracy_label = QLabel()stats_layout.addRow("总分:", self.total_score_label)stats_layout.addRow("正确率:", self.accuracy_label)layout.addWidget(self.stats_group)# 错题按钮self.btn_wrong_answers = QPushButton("查看错题")self.btn_wrong_answers.setStyleSheet("background-color: #e74c3c;")self.btn_wrong_answers.clicked.connect(self.show_wrong_answers)layout.addWidget(self.btn_wrong_answers, alignment=Qt.AlignCenter)# 按钮区域btn_layout = QHBoxLayout()self.btn_retry = QPushButton("🔄 重新测验")self.btn_retry.clicked.connect(self.retry_exam)self.btn_back_result = QPushButton("🏠 返回主页")self.btn_back_result.clicked.connect(self.show_main_page)btn_layout.addWidget(self.btn_retry)btn_layout.addWidget(self.btn_back_result)layout.addLayout(btn_layout)return pagedef create_wrong_questions_page(self):"""创建错题集页面"""page = QWidget()layout = QVBoxLayout(page)# 标题title = QLabel("❌ 错题集")title.setStyleSheet("""QLabel {font-size: 20px;font-weight: bold;margin-bottom: 20px;}""")title.setAlignment(Qt.AlignCenter)layout.addWidget(title)# 题库选择bank_layout = QHBoxLayout()bank_layout.addWidget(QLabel("选择题库:"))self.wrong_bank_combo = QComboBox()self.wrong_bank_combo.currentIndexChanged.connect(self.update_wrong_list)bank_layout.addWidget(self.wrong_bank_combo)layout.addLayout(bank_layout)# 错题列表self.wrong_tree = QTreeWidget()self.wrong_tree.setHeaderLabels(["题型", "题目", "正确答案", "你的答案"])self.wrong_tree.setColumnWidth(0, 80)self.wrong_tree.setColumnWidth(1, 400)self.wrong_tree.setColumnWidth(2, 100)self.wrong_tree.setColumnWidth(3, 100)self.wrong_tree.setSelectionMode(QAbstractItemView.SingleSelection)layout.addWidget(self.wrong_tree)# 按钮区域btn_layout = QHBoxLayout()self.btn_detail = QPushButton("🔍 查看详情")self.btn_detail.clicked.connect(self.show_wrong_detail)self.btn_delete_wrong = QPushButton("🗑️ 删除错题")self.btn_delete_wrong.clicked.connect(self.delete_wrong_question)self.btn_back_wrong = QPushButton("🔙 返回")self.btn_back_wrong.clicked.connect(self.back_from_wrong_page)buttons = [self.btn_detail, self.btn_delete_wrong, self.btn_back_wrong]for btn in buttons:btn_layout.addWidget(btn)layout.addLayout(btn_layout)return pagedef create_stats_page(self):"""创建学习统计页面"""page = QWidget()layout = QVBoxLayout(page)# 标题title = QLabel("📊 学习统计")title.setStyleSheet("""QLabel {font-size: 20px;font-weight: bold;margin-bottom: 20px;}""")title.setAlignment(Qt.AlignCenter)layout.addWidget(title)# 图表区域self.stats_canvas = FigureCanvas(plt.Figure(figsize=(10, 5)))layout.addWidget(self.stats_canvas)# 返回按钮self.btn_back_stats = QPushButton("🔙 返回")self.btn_back_stats.clicked.connect(self.show_main_page)layout.addWidget(self.btn_back_stats, alignment=Qt.AlignRight)return pagedef create_practice_setup_page(self):"""创建刷题设置页面"""page = QWidget()layout = QVBoxLayout(page)# 标题title = QLabel("🌊 题海刷题模式")title.setStyleSheet("""QLabel {font-size: 20px;font-weight: bold;margin-bottom: 20px;}""")title.setAlignment(Qt.AlignCenter)layout.addWidget(title)# 题库选择form_layout = QFormLayout()self.practice_bank_combo = QComboBox()form_layout.addRow("选择题库:", self.practice_bank_combo)# 刷题顺序选择self.order_combo = QComboBox()self.order_combo.addItems(["顺序", "逆序", "随机"])form_layout.addRow("刷题顺序:", self.order_combo)layout.addLayout(form_layout)# 按钮区域btn_layout = QHBoxLayout()self.btn_start_practice = QPushButton("开始刷题")self.btn_start_practice.clicked.connect(self.start_practice)self.btn_back_practice = QPushButton("返回")self.btn_back_practice.clicked.connect(self.show_main_page)btn_layout.addWidget(self.btn_start_practice)btn_layout.addWidget(self.btn_back_practice)layout.addLayout(btn_layout)return pagedef create_practice_page(self):"""创建刷题页面"""page = QWidget()layout = QVBoxLayout(page)# 进度显示self.practice_progress_label = QLabel()self.practice_progress_label.setStyleSheet("font-size: 16px; font-weight: bold;")layout.addWidget(self.practice_progress_label, alignment=Qt.AlignLeft)# 题目内容self.practice_question_group = QGroupBox()self.practice_question_group.setStyleSheet("QGroupBox { font-size: 14px; }")question_layout = QVBoxLayout(self.practice_question_group)self.practice_question_type_label = QLabel()self.practice_question_type_label.setStyleSheet("font-weight: bold; color: #3498db;")self.practice_question_text = QLabel()self.practice_question_text.setWordWrap(True)self.practice_question_text.setStyleSheet("font-size: 15px;")self.practice_options_layout = QVBoxLayout()question_layout.addWidget(self.practice_question_type_label)question_layout.addWidget(self.practice_question_text)question_layout.addLayout(self.practice_options_layout)question_layout.addStretch()layout.addWidget(self.practice_question_group)# 答案反馈self.answer_feedback = QLabel()self.answer_feedback.setStyleSheet("font-size: 14px; font-weight: bold;")self.answer_feedback.setWordWrap(True)layout.addWidget(self.answer_feedback)# 按钮区域btn_layout = QHBoxLayout()self.btn_prev_practice = QPushButton("⬅️ 上一题")self.btn_prev_practice.clicked.connect(self.prev_practice_question)self.btn_next_practice = QPushButton("➡️ 下一题")self.btn_next_practice.clicked.connect(self.next_practice_question)self.btn_submit_practice = QPushButton("提交答案")self.btn_submit_practice.clicked.connect(self.submit_practice_answer)self.btn_back_practice_page = QPushButton("🔙 返回")self.btn_back_practice_page.clicked.connect(self.confirm_exit_practice)buttons = [self.btn_prev_practice, self.btn_next_practice, self.btn_submit_practice, self.btn_back_practice_page]for btn in buttons:btn_layout.addWidget(btn)layout.addLayout(btn_layout)return page# 页面导航方法def show_main_page(self):"""显示主页面"""self.content_area.setCurrentWidget(self.main_page)def show_bank_manager(self):"""显示题库管理页面"""self.bank_list.clear()for bank_name in self.question_banks.keys():self.bank_list.addItem(bank_name)self.content_area.setCurrentWidget(self.bank_manager_page)def show_bank_editor(self, bank_name):"""显示题库编辑器页面"""self.bank_editor_title.setText(f"✏️ 编辑题库: {bank_name}")self.current_question_bank = bank_nameself.question_tree.clear()for question in self.question_banks[bank_name]:q_type = question['type']q_text = question['question']options = "\n".join(question['options']) if 'options' in question else ""answer = ", ".join(question['answer']) if isinstance(question['answer'], list) else question['answer']score = question.get('score', 1)item = QTreeWidgetItem(self.question_tree)item.setText(0, q_type)item.setText(1, q_text)item.setText(2, options)item.setText(3, answer)item.setText(4, str(score))self.content_area.setCurrentWidget(self.bank_editor_page)def show_exam_setup(self):"""显示组卷设置页面"""self.bank_combo.clear()self.bank_combo.addItems(self.question_banks.keys())if self.current_question_bank and self.current_question_bank in self.question_banks:index = self.bank_combo.findText(self.current_question_bank)if index >= 0:self.bank_combo.setCurrentIndex(index)self.content_area.setCurrentWidget(self.exam_setup_page)def show_practice_setup(self):"""显示刷题设置页面"""self.practice_bank_combo.clear()self.practice_bank_combo.addItems(self.question_banks.keys())self.content_area.setCurrentWidget(self.practice_setup_page)def show_question(self):"""显示当前题目"""if not self.current_questions:QMessageBox.warning(self, "错误", "没有可用的题目!")returnself.content_area.setCurrentWidget(self.exam_page)# 显示计时器self.timer_layout.parentWidget().show()self.update_exam_timer()# 更新进度self.progress_label.setText(f"题目 {self.current_index + 1}/{len(self.current_questions)}")# 获取当前题目current_question = self.current_questions[self.current_index]q_type = current_question['type']q_text = current_question['question']options = current_question.get('options', [])q_score = current_question.get('score', 1)# 显示题目信息self.question_type_label.setText(f"[{q_type}] (分值: {q_score})")self.question_text.setText(q_text)# 清除之前的选项while self.options_layout.count():child = self.options_layout.takeAt(0)if child.widget():child.widget().deleteLater()# 用户答案变量if q_type == "单选题":self.user_answer_rbs = []for i, option in enumerate(options):rb = QRadioButton(f"{chr(65+i)}. {option}")if str(self.current_index) in self.user_answers:if self.user_answers[str(self.current_index)] == chr(65+i):rb.setChecked(True)self.user_answer_rbs.append(rb)self.options_layout.addWidget(rb)elif q_type == "多选题":self.user_answer_cbs = []for i, option in enumerate(options):cb = QCheckBox(f"{chr(65+i)}. {option}")if str(self.current_index) in self.user_answers:if chr(65+i) in self.user_answers[str(self.current_index)]:cb.setChecked(True)self.user_answer_cbs.append(cb)self.options_layout.addWidget(cb)elif q_type == "判断题":self.user_answer_rbs = []rb_true = QRadioButton("正确")rb_false = QRadioButton("错误")if str(self.current_index) in self.user_answers:if self.user_answers[str(self.current_index)] == "正确":rb_true.setChecked(True)else:rb_false.setChecked(True)self.user_answer_rbs.append(rb_true)self.user_answer_rbs.append(rb_false)self.options_layout.addWidget(rb_true)self.options_layout.addWidget(rb_false)# 更新按钮状态self.btn_prev.setEnabled(self.current_index > 0)if self.current_index < len(self.current_questions) - 1:self.btn_next.show()self.btn_submit.hide()else:self.btn_next.hide()self.btn_submit.show()def show_practice_question(self):"""显示刷题题目"""if not self.current_questions:QMessageBox.warning(self, "错误", "没有可用的题目!")returnself.content_area.setCurrentWidget(self.practice_page)# 更新进度self.practice_progress_label.setText(f"题目 {self.current_index + 1}/{len(self.current_questions)}")self.answer_feedback.clear()# 获取当前题目current_question = self.current_questions[self.current_index]q_type = current_question['type']q_text = current_question['question']options = current_question.get('options', [])# 显示题目信息self.practice_question_type_label.setText(f"[{q_type}]")self.practice_question_text.setText(q_text)# 清除之前的选项while self.practice_options_layout.count():child = self.practice_options_layout.takeAt(0)if child.widget():child.widget().deleteLater()# 用户答案变量if q_type == "单选题":self.user_answer_rbs = []for i, option in enumerate(options):rb = QRadioButton(f"{chr(65+i)}. {option}")if str(self.current_index) in self.user_answers:if self.user_answers[str(self.current_index)] == chr(65+i):rb.setChecked(True)self.user_answer_rbs.append(rb)self.practice_options_layout.addWidget(rb)elif q_type == "多选题":self.user_answer_cbs = []for i, option in enumerate(options):cb = QCheckBox(f"{chr(65+i)}. {option}")if str(self.current_index) in self.user_answers:if chr(65+i) in self.user_answers[str(self.current_index)]:cb.setChecked(True)self.user_answer_cbs.append(cb)self.practice_options_layout.addWidget(cb)elif q_type == "判断题":self.user_answer_rbs = []rb_true = QRadioButton("正确")rb_false = QRadioButton("错误")if str(self.current_index) in self.user_answers:if self.user_answers[str(self.current_index)] == "正确":rb_true.setChecked(True)else:rb_false.setChecked(True)self.user_answer_rbs.append(rb_true)self.user_answer_rbs.append(rb_false)self.practice_options_layout.addWidget(rb_true)self.practice_options_layout.addWidget(rb_false)# 更新按钮状态self.btn_prev_practice.setEnabled(self.current_index > 0)self.btn_next_practice.setEnabled(self.current_index < len(self.current_questions) - 1)self.btn_submit_practice.setEnabled(True)def show_exam_result(self, correct_count):"""显示测验结果"""total_questions = len(self.current_questions)accuracy = (correct_count / total_questions) * 100 if total_questions > 0 else 0self.total_score_label.setText(f"{self.score}/{self.total_score}")self.accuracy_label.setText(f"{accuracy:.1f}% ({correct_count}/{total_questions})")# 如果没有错题,隐藏查看错题按钮self.btn_wrong_answers.setVisible(correct_count < total_questions)self.content_area.setCurrentWidget(self.result_page)def show_wrong_questions(self):"""显示错题集页面"""if not self.wrong_questions:QMessageBox.information(self, "提示", "错题集为空!")returnself.wrong_bank_combo.clear()self.wrong_bank_combo.addItems(self.wrong_questions.keys())self.update_wrong_list()self.content_area.setCurrentWidget(self.wrong_questions_page)def show_stats(self):"""显示学习统计页面"""if not self.wrong_questions:QMessageBox.information(self, "提示", "暂无统计数据!")return# 创建图表fig = self.stats_canvas.figurefig.clear()ax1 = fig.add_subplot(121)ax2 = fig.add_subplot(122)# 设置中文字体plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号# 错题题型分布type_counts = {'单选题': 0, '多选题': 0, '判断题': 0}for bank_name, questions in self.wrong_questions.items():for question in questions:q_type = question['type']type_counts[q_type] += 1labels = list(type_counts.keys())sizes = list(type_counts.values())ax1.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90)ax1.set_title('错题题型分布')# 各题库错题数量bank_names = list(self.wrong_questions.keys())wrong_counts = [len(questions) for questions in self.wrong_questions.values()]ax2.bar(bank_names, wrong_counts)ax2.set_title('各题库错题数量')ax2.set_ylabel('数量')ax2.tick_params(axis='x', rotation=45)fig.tight_layout()self.stats_canvas.draw()self.content_area.setCurrentWidget(self.stats_page)# 题库管理相关方法def import_question_bank(self):"""导入题库文件"""filename, _ = QFileDialog.getOpenFileName(self,"导入题库","","题库文件 (*.json *.xlsx)")if not filename:returntry:if filename.endswith('.json'):with open(filename, 'r', encoding='utf-8') as f:bank_name = filename.split('/')[-1].replace('.json', '')self.question_banks[bank_name] = json.load(f)elif filename.endswith('.xlsx'):bank_name = filename.split('/')[-1].replace('.xlsx', '')self.question_banks[bank_name] = self.load_excel_bank(filename)QMessageBox.information(self, "成功", f"题库 '{bank_name}' 导入成功!")self.show_bank_manager()except Exception as e:QMessageBox.warning(self, "错误", f"导入失败: {str(e)}")def create_new_bank(self):"""创建新题库"""bank_name, ok = QInputDialog.getText(self, "新建题库", "请输入题库名称:")if ok and bank_name:if bank_name in self.question_banks:QMessageBox.warning(self, "错误", "题库已存在!")else:self.question_banks[bank_name] = []self.bank_list.addItem(bank_name)QMessageBox.information(self, "成功", f"题库 '{bank_name}' 创建成功!")def load_selected_bank(self):"""加载选中的题库"""selected_items = self.bank_list.selectedItems()if not selected_items:QMessageBox.warning(self, "错误", "请先选择一个题库!")returnbank_name = selected_items[0].text()self.current_question_bank = bank_nameQMessageBox.information(self, "成功", f"题库 '{bank_name}' 已加载!")def edit_selected_bank(self):"""编辑选中的题库"""selected_items = self.bank_list.selectedItems()if not selected_items:QMessageBox.warning(self, "错误", "请先选择一个题库!")returnbank_name = selected_items[0].text()self.show_bank_editor(bank_name)def export_selected_bank(self):"""导出选中的题库"""selected_items = self.bank_list.selectedItems()if not selected_items:QMessageBox.warning(self, "错误", "请先选择一个题库!")returnbank_name = selected_items[0].text()# 选择导出格式formats = ["JSON (.json)", "Excel (.xlsx)"]format_choice, ok = QInputDialog.getItem(self, "选择导出格式", "请选择导出格式:", formats, 0, False)if ok:export_format = "json" if "JSON" in format_choice else "xlsx"self.save_question_bank(bank_name, export_format)def delete_selected_bank(self):"""删除选中的题库"""selected_items = self.bank_list.selectedItems()if not selected_items:QMessageBox.warning(self, "错误", "请先选择一个题库!")returnbank_name = selected_items[0].text()reply = QMessageBox.question(self, "确认", f"确定要删除题库 '{bank_name}' 吗?", QMessageBox.Yes | QMessageBox.No)if reply == QMessageBox.Yes:# 从内存中删除del self.question_banks[bank_name]# 从列表框中删除self.bank_list.takeItem(self.bank_list.row(selected_items[0]))QMessageBox.information(self, "成功", f"题库 '{bank_name}' 已删除!")def add_question_dialog(self):"""添加题目对话框"""if not self.current_question_bank:returndialog = QDialog(self)dialog.setWindowTitle("添加题目")dialog.setModal(True)dialog.resize(600, 500)layout = QVBoxLayout(dialog)# 题型选择type_layout = QHBoxLayout()type_layout.addWidget(QLabel("题型:"))q_type_combo = QComboBox()q_type_combo.addItems(["单选题", "多选题", "判断题"])type_layout.addWidget(q_type_combo)layout.addLayout(type_layout)# 题目内容layout.addWidget(QLabel("题目:"))question_edit = QTextEdit()layout.addWidget(question_edit)# 选项框架options_group = QGroupBox("选项")options_layout = QVBoxLayout(options_group)self.option_edits = []for i in range(4):option_layout = QHBoxLayout()option_layout.addWidget(QLabel(f"选项 {chr(65+i)}:"))option_edit = QLineEdit()self.option_edits.append(option_edit)option_layout.addWidget(option_edit)options_layout.addLayout(option_layout)layout.addWidget(options_group)# 答案框架answer_group = QGroupBox("答案")answer_layout = QVBoxLayout(answer_group)# 单选题答案self.single_answer_combo = QComboBox()self.single_answer_combo.addItems(["A", "B", "C", "D"])# 多选题答案self.multi_answer_checks = []multi_layout = QHBoxLayout()for i in range(4):cb = QCheckBox(chr(65+i))self.multi_answer_checks.append(cb)multi_layout.addWidget(cb)# 判断题答案self.bool_answer_combo = QComboBox()self.bool_answer_combo.addItems(["正确", "错误"])# 默认显示单选题答案answer_layout.addWidget(self.single_answer_combo)# 分值score_layout = QHBoxLayout()score_layout.addWidget(QLabel("分值:"))score_edit = QLineEdit("1")score_edit.setValidator(QtGui.QIntValidator(1, 10))score_layout.addWidget(score_edit)layout.addWidget(answer_group)layout.addLayout(score_layout)# 按钮btn_layout = QHBoxLayout()add_btn = QPushButton("添加")add_btn.clicked.connect(lambda: self.add_question(dialog, q_type_combo, question_edit, score_edit))cancel_btn = QPushButton("取消")cancel_btn.clicked.connect(dialog.reject)btn_layout.addWidget(add_btn)btn_layout.addWidget(cancel_btn)layout.addLayout(btn_layout)# 题型变化事件def on_type_change(index):# 隐藏所有答案控件self.single_answer_combo.hide()for cb in self.multi_answer_checks:cb.hide()self.bool_answer_combo.hide()selected_type = q_type_combo.currentText()if selected_type == "单选题":self.single_answer_combo.show()elif selected_type == "多选题":for i, cb in enumerate(self.multi_answer_checks):cb.show()if i >= 4:cb.hide()elif selected_type == "判断题":self.bool_answer_combo.show()# 显示/隐藏选项for i, edit in enumerate(self.option_edits):if selected_type == "判断题" and i >= 2:edit.hide()edit.parent().hide()else:edit.show()edit.parent().show()q_type_combo.currentIndexChanged.connect(on_type_change)on_type_change(0)  # 初始化dialog.exec_()def add_question(self, dialog, q_type_combo, question_edit, score_edit):"""添加题目到题库"""q_type = q_type_combo.currentText()q_text = question_edit.toPlainText().strip()options = []if q_type == "判断题":options = ["正确", "错误"]else:for edit in self.option_edits[:4 if q_type != "判断题" else 2]:option = edit.text().strip()if option:options.append(option)if not q_text:QMessageBox.warning(dialog, "错误", "题目不能为空!")returnif not options:QMessageBox.warning(dialog, "错误", "至少需要一个选项!")return# 获取答案if q_type == "单选题":answer = self.single_answer_combo.currentText()elif q_type == "多选题":answer = []for i, cb in enumerate(self.multi_answer_checks):if cb.isChecked():answer.append(chr(65+i))answer = ", ".join(answer)elif q_type == "判断题":answer = self.bool_answer_combo.currentText()if not answer:QMessageBox.warning(dialog, "错误", "请设置正确答案!")returntry:score = int(score_edit.text())except ValueError:score = 1# 创建题目字典new_question = {'type': q_type,'question': q_text,'options': options,'answer': answer,'score': score}# 添加到题库self.question_banks[self.current_question_bank].append(new_question)# 更新树形视图item = QTreeWidgetItem(self.question_tree)item.setText(0, q_type)item.setText(1, q_text)item.setText(2, "\n".join(options))item.setText(3, answer)item.setText(4, str(score))QMessageBox.information(dialog, "成功", "题目添加成功!")dialog.accept()def edit_question_dialog(self):"""编辑题目对话框"""selected_items = self.question_tree.selectedItems()if not selected_items:QMessageBox.warning(self, "错误", "请先选择要编辑的题目!")returnitem = selected_items[0]q_type = item.text(0)q_text = item.text(1)options = item.text(2).split("\n")answer = item.text(3)score = item.text(4)# 找到题库中的原始题目index = self.question_tree.indexOfTopLevelItem(item)original_question = self.question_banks[self.current_question_bank][index]dialog = QDialog(self)dialog.setWindowTitle("编辑题目")dialog.setModal(True)dialog.resize(600, 500)layout = QVBoxLayout(dialog)# 题型选择type_layout = QHBoxLayout()type_layout.addWidget(QLabel("题型:"))q_type_combo = QComboBox()q_type_combo.addItems(["单选题", "多选题", "判断题"])q_type_combo.setCurrentText(q_type)type_layout.addWidget(q_type_combo)layout.addLayout(type_layout)# 题目内容layout.addWidget(QLabel("题目:"))question_edit = QTextEdit()question_edit.setText(q_text)layout.addWidget(question_edit)# 选项框架options_group = QGroupBox("选项")options_layout = QVBoxLayout(options_group)self.option_edits = []for i in range(4):option_layout = QHBoxLayout()option_layout.addWidget(QLabel(f"选项 {chr(65+i)}:"))option_edit = QLineEdit()if i < len(options):option_edit.setText(options[i])self.option_edits.append(option_edit)option_layout.addWidget(option_edit)options_layout.addLayout(option_layout)layout.addWidget(options_group)# 答案框架answer_group = QGroupBox("答案")answer_layout = QVBoxLayout(answer_group)# 单选题答案self.single_answer_combo = QComboBox()self.single_answer_combo.addItems(["A", "B", "C", "D"])# 多选题答案self.multi_answer_checks = []multi_layout = QHBoxLayout()for i in range(4):cb = QCheckBox(chr(65+i))self.multi_answer_checks.append(cb)multi_layout.addWidget(cb)# 判断题答案self.bool_answer_combo = QComboBox()self.bool_answer_combo.addItems(["正确", "错误"])# 设置当前答案if q_type == "单选题":self.single_answer_combo.setCurrentText(answer)answer_layout.addWidget(self.single_answer_combo)elif q_type == "多选题":for char in answer.split(", "):if char:idx = ord(char.upper()) - ord('A')if 0 <= idx < 4:self.multi_answer_checks[idx].setChecked(True)answer_layout.addLayout(multi_layout)elif q_type == "判断题":self.bool_answer_combo.setCurrentText(answer)answer_layout.addWidget(self.bool_answer_combo)# 分值score_layout = QHBoxLayout()score_layout.addWidget(QLabel("分值:"))score_edit = QLineEdit(score)score_edit.setValidator(QtGui.QIntValidator(1, 10))score_layout.addWidget(score_edit)layout.addWidget(answer_group)layout.addLayout(score_layout)# 按钮btn_layout = QHBoxLayout()save_btn = QPushButton("保存")save_btn.clicked.connect(lambda: self.save_question_edit(dialog, q_type_combo, question_edit, score_edit, index))cancel_btn = QPushButton("取消")cancel_btn.clicked.connect(dialog.reject)btn_layout.addWidget(save_btn)btn_layout.addWidget(cancel_btn)layout.addLayout(btn_layout)# 题型变化事件def on_type_change(index):# 隐藏所有答案控件self.single_answer_combo.hide()for cb in self.multi_answer_checks:cb.hide()self.bool_answer_combo.hide()selected_type = q_type_combo.currentText()if selected_type == "单选题":self.single_answer_combo.show()elif selected_type == "多选题":for i, cb in enumerate(self.multi_answer_checks):cb.show()if i >= 4:cb.hide()elif selected_type == "判断题":self.bool_answer_combo.show()# 显示/隐藏选项for i, edit in enumerate(self.option_edits):if selected_type == "判断题" and i >= 2:edit.hide()edit.parent().hide()else:edit.show()edit.parent().show()q_type_combo.currentIndexChanged.connect(on_type_change)dialog.exec_()def save_question_edit(self, dialog, q_type_combo, question_edit, score_edit, index):"""保存编辑后的题目"""q_type = q_type_combo.currentText()q_text = question_edit.toPlainText().strip()options = []if q_type == "判断题":options = ["正确", "错误"]else:for edit in self.option_edits[:4 if q_type != "判断题" else 2]:option = edit.text().strip()if option:options.append(option)if not q_text:QMessageBox.warning(dialog, "错误", "题目不能为空!")returnif not options:QMessageBox.warning(dialog, "错误", "至少需要一个选项!")return# 获取答案if q_type == "单选题":answer = self.single_answer_combo.currentText()elif q_type == "多选题":answer = []for i, cb in enumerate(self.multi_answer_checks):if cb.isChecked():answer.append(chr(65+i))answer = ", ".join(answer)elif q_type == "判断题":answer = self.bool_answer_combo.currentText()if not answer:QMessageBox.warning(dialog, "错误", "请设置正确答案!")returntry:score = int(score_edit.text())except ValueError:score = 1# 更新题目字典updated_question = {'type': q_type,'question': q_text,'options': options,'answer': answer,'score': score}# 更新题库self.question_banks[self.current_question_bank][index] = updated_question# 更新树形视图item = self.question_tree.topLevelItem(index)item.setText(0, q_type)item.setText(1, q_text)item.setText(2, "\n".join(options))item.setText(3, answer)item.setText(4, str(score))QMessageBox.information(dialog, "成功", "题目更新成功!")dialog.accept()def delete_question(self):"""删除题目"""selected_items = self.question_tree.selectedItems()if not selected_items:QMessageBox.warning(self, "错误", "请先选择要删除的题目!")returnreply = QMessageBox.question(self, "确认", "确定要删除选中的题目吗?", QMessageBox.Yes | QMessageBox.No)if reply == QMessageBox.No:return# 从树形视图中删除index = self.question_tree.indexOfTopLevelItem(selected_items[0])self.question_tree.takeTopLevelItem(index)# 从题库中删除if index < len(self.question_banks[self.current_question_bank]):self.question_banks[self.current_question_bank].pop(index)QMessageBox.information(self, "成功", "题目已删除!")def save_question_bank(self, bank_name=None, export_format="json"):"""保存题库到文件"""if not bank_name:bank_name = self.current_question_bankif export_format == "json":filename, _ = QFileDialog.getSaveFileName(self,"保存题库",f"{bank_name}.json","JSON 文件 (*.json)")if filename:try:with open(filename, 'w', encoding='utf-8') as f:json.dump(self.question_banks[bank_name], f, ensure_ascii=False, indent=4)QMessageBox.information(self, "成功", f"题库 '{bank_name}' 已保存为JSON格式!")except Exception as e:QMessageBox.warning(self, "错误", f"保存失败: {e}")else:  # Excel格式filename, _ = QFileDialog.getSaveFileName(self,"导出题库",f"{bank_name}.xlsx","Excel 文件 (*.xlsx)")if filename:try:self.export_to_excel(bank_name, filename)QMessageBox.information(self, "成功", f"题库 '{bank_name}' 已导出为Excel格式!")except Exception as e:QMessageBox.warning(self, "错误", f"导出失败: {e}")def export_to_excel(self, bank_name, filename):"""将题库导出为Excel文件"""workbook = openpyxl.Workbook()sheet = workbook.activesheet.title = "题库"# 写入表头headers = ["题型", "题目", "选项A", "选项B", "选项C", "选项D", "答案", "分值"]sheet.append(headers)# 写入题目for question in self.question_banks[bank_name]:row = [question["type"],question["question"]]# 添加选项options = question.get("options", [])for i in range(4):row.append(options[i] if i < len(options) else "")# 添加答案和分值row.append(question["answer"])row.append(question.get("score", 1))sheet.append(row)workbook.save(filename)def load_question_bank_list(self):"""加载题库列表"""# 尝试加载本地题库文件self.load_local_banks()# 如果没有加载到题库,使用示例题库if not self.question_banks:self.question_banks = {"示例题库": [{"type": "单选题","question": "Python是什么类型的语言?","options": ["编译型", "解释型", "混合型", "汇编型"],"answer": "B","score": 2},{"type": "多选题","question": "以下哪些是Python的数据类型?","options": ["int", "float", "string", "boolean"],"answer": "A, B, C, D","score": 3},{"type": "判断题","question": "Python是强类型语言。","options": ["正确", "错误"],"answer": "正确","score": 1}]}def load_local_banks(self):"""加载本地题库文件(.json和.xlsx)"""# 加载JSON题库json_files = self.find_local_files(".json")for file in json_files:try:with open(file, 'r', encoding='utf-8') as f:bank_name = file.split('/')[-1].replace('.json', '')self.question_banks[bank_name] = json.load(f)except Exception as e:print(f"加载JSON题库失败: {file}, 错误: {e}")# 加载Excel题库excel_files = self.find_local_files(".xlsx")for file in excel_files:try:bank_name = file.split('/')[-1].replace('.xlsx', '')self.question_banks[bank_name] = self.load_excel_bank(file)except Exception as e:print(f"加载Excel题库失败: {file}, 错误: {e}")def find_local_files(self, extension):"""查找本地题库文件"""import osfiles = []for file in os.listdir('.'):if file.endswith(extension):files.append(file)return filesdef load_excel_bank(self, file_path):"""从Excel文件加载题库"""workbook = openpyxl.load_workbook(file_path)sheet = workbook.activequestions = []for row in sheet.iter_rows(min_row=2, values_only=True):if not row[0]:  # 跳过空行continueq_type = row[0]question = {"type": q_type,"question": row[1],"options": [],"answer": row[-2],"score": row[-1] if row[-1] else 1}# 处理选项if q_type in ["单选题", "多选题"]:question["options"] = [opt for opt in row[2:-2] if opt]elif q_type == "判断题":question["options"] = ["正确", "错误"]questions.append(question)return questions# 考试相关方法def start_exam(self):"""开始考试"""bank_name = self.bank_combo.currentText()if not bank_name:QMessageBox.warning(self, "错误", "请选择题库!")return# 加载题库self.current_question_bank = bank_namequestions = self.question_banks[bank_name]if not questions:QMessageBox.warning(self, "错误", "题库中没有题目!")returntry:num_questions = int(self.num_questions.text())single_ratio = int(self.single_ratio.text())multi_ratio = int(self.multi_ratio.text())bool_ratio = int(self.bool_ratio.text())if single_ratio + multi_ratio + bool_ratio != 100:QMessageBox.warning(self, "错误", "题型比例总和必须为100%!")returnexcept ValueError:QMessageBox.warning(self, "错误", "请输入有效的数字!")return# 按题型分类题目single_questions = [q for q in questions if q['type'] == "单选题"]multi_questions = [q for q in questions if q['type'] == "多选题"]bool_questions = [q for q in questions if q['type'] == "判断题"]# 计算各题型题目数量single_count = int(num_questions * single_ratio / 100)multi_count = int(num_questions * multi_ratio / 100)bool_count = num_questions - single_count - multi_count# 随机选择题目selected_questions = []if single_count > 0:if len(single_questions) >= single_count:selected_questions.extend(random.sample(single_questions, single_count))else:selected_questions.extend(single_questions)if multi_count > 0:if len(multi_questions) >= multi_count:selected_questions.extend(random.sample(multi_questions, multi_count))else:selected_questions.extend(multi_questions)if bool_count > 0:if len(bool_questions) >= bool_count:selected_questions.extend(random.sample(bool_questions, bool_count))else:selected_questions.extend(bool_questions)# 如果题目不足,补充随机题目if len(selected_questions) < num_questions:remaining = num_questions - len(selected_questions)all_questions = [q for q in questions if q not in selected_questions]if all_questions:selected_questions.extend(random.sample(all_questions, min(remaining, len(all_questions))))# 随机排序random.shuffle(selected_questions)self.total_score = sum(q.get('score', 1) for q in selected_questions)# 设置考试计时器try:time_limit = int(self.time_limit.text())self.exam_mode = Trueself.exam_start_time = datetime.now()self.exam_time_limit = time_limit * 60  # 转换为秒except ValueError:QMessageBox.warning(self, "错误", "请输入有效的时间限制!")return# 设置当前题目self.current_questions = selected_questionsself.current_index = 0self.user_answers = {}self.score = 0# 显示题目self.show_question()def update_exam_timer(self):"""更新考试计时器"""if not self.exam_mode:returnelapsed = (datetime.now() - self.exam_start_time).total_seconds()remaining = max(0, self.exam_time_limit - elapsed)minutes = int(remaining // 60)seconds = int(remaining % 60)self.timer_label.setText(f"剩余时间: {minutes:02d}:{seconds:02d}")if remaining <= 0:self.submit_exam()else:QTimer.singleShot(1000, self.update_exam_timer)def prev_question(self):"""显示上一题"""self.save_current_answer()if self.current_index > 0:self.current_index -= 1self.show_question()def next_question(self):"""显示下一题"""self.save_current_answer()if self.current_index < len(self.current_questions) - 1:self.current_index += 1self.show_question()else:self.submit_exam()def save_current_answer(self):"""保存当前题目的答案"""current_question = self.current_questions[self.current_index]q_type = current_question['type']if q_type == "单选题":for i, rb in enumerate(self.user_answer_rbs):if rb.isChecked():self.user_answers[str(self.current_index)] = chr(65+i)breakelif q_type == "多选题":answer = []for i, cb in enumerate(self.user_answer_cbs):if cb.isChecked():answer.append(chr(65+i))if answer:self.user_answers[str(self.current_index)] = "".join(answer)elif q_type == "判断题":if self.user_answer_rbs[0].isChecked():self.user_answers[str(self.current_index)] = "正确"elif self.user_answer_rbs[1].isChecked():self.user_answers[str(self.current_index)] = "错误"def submit_exam(self):"""提交试卷"""self.save_current_answer()self.exam_mode = False# 计算得分correct_count = 0self.score = 0for i, question in enumerate(self.current_questions):user_answer = self.user_answers.get(str(i), "")correct_answer = question['answer']q_score = question.get('score', 1)if question['type'] == "多选题":# 对多选题答案进行排序比较user_answer_sorted = "".join(sorted(user_answer.upper()))correct_answer_sorted = "".join(sorted(correct_answer.replace(", ", "").upper()))is_correct = user_answer_sorted == correct_answer_sortedelse:is_correct = str(user_answer).upper() == str(correct_answer).upper()if is_correct:self.score += q_scorecorrect_count += 1else:# 添加到错题集if self.current_question_bank not in self.wrong_questions:self.wrong_questions[self.current_question_bank] = []wrong_question = question.copy()wrong_question['user_answer'] = user_answerwrong_question['index'] = iself.wrong_questions[self.current_question_bank].append(wrong_question)# 显示结果self.show_exam_result(correct_count)def retry_exam(self):"""重新测验"""self.start_exam()def confirm_exit_exam(self):"""确认退出考试"""reply = QMessageBox.question(self, "确认", "确定要退出当前考试吗? 所有进度将丢失!", QMessageBox.Yes | QMessageBox.No)if reply == QMessageBox.Yes:self.show_main_page()# 刷题相关方法def start_practice(self):"""开始刷题"""bank_name = self.practice_bank_combo.currentText()if not bank_name:QMessageBox.warning(self, "错误", "请选择题库!")return# 加载题库self.current_question_bank = bank_namequestions = self.question_banks[bank_name]if not questions:QMessageBox.warning(self, "错误", "题库中没有题目!")return# 设置刷题顺序self.practice_order = self.order_combo.currentText()# 准备题目self.current_questions = questions.copy()if self.practice_order == "逆序":self.current_questions.reverse()elif self.practice_order == "随机":random.shuffle(self.current_questions)self.current_index = 0self.user_answers = {}self.practice_mode = True# 显示第一题self.show_practice_question()def submit_practice_answer(self):"""提交刷题答案"""self.save_current_practice_answer()current_question = self.current_questions[self.current_index]user_answer = self.user_answers.get(str(self.current_index), "")correct_answer = current_question['answer']# 检查答案if current_question['type'] == "多选题":# 对多选题答案进行排序比较user_answer_sorted = "".join(sorted(user_answer.upper()))correct_answer_sorted = "".join(sorted(correct_answer.replace(", ", "").upper()))is_correct = user_answer_sorted == correct_answer_sortedelse:is_correct = str(user_answer).upper() == str(correct_answer).upper()# 显示反馈if is_correct:self.answer_feedback.setText("✅ 回答正确!")self.answer_feedback.setStyleSheet("color: green;")else:self.answer_feedback.setText(f"❌ 回答错误! 正确答案是: {correct_answer}")self.answer_feedback.setStyleSheet("color: red;")# 记录错题if self.current_question_bank not in self.wrong_questions:self.wrong_questions[self.current_question_bank] = []wrong_question = current_question.copy()wrong_question['user_answer'] = user_answerself.wrong_questions[self.current_question_bank].append(wrong_question)self.btn_submit_practice.setEnabled(False)def save_current_practice_answer(self):"""保存当前刷题答案"""current_question = self.current_questions[self.current_index]q_type = current_question['type']if q_type == "单选题":for i, rb in enumerate(self.user_answer_rbs):if rb.isChecked():self.user_answers[str(self.current_index)] = chr(65+i)breakelif q_type == "多选题":answer = []for i, cb in enumerate(self.user_answer_cbs):if cb.isChecked():answer.append(chr(65+i))if answer:self.user_answers[str(self.current_index)] = "".join(answer)elif q_type == "判断题":if self.user_answer_rbs[0].isChecked():self.user_answers[str(self.current_index)] = "正确"elif self.user_answer_rbs[1].isChecked():self.user_answers[str(self.current_index)] = "错误"def prev_practice_question(self):"""上一题"""if self.current_index > 0:self.current_index -= 1self.show_practice_question()def next_practice_question(self):"""下一题"""if self.current_index < len(self.current_questions) - 1:self.current_index += 1self.show_practice_question()def confirm_exit_practice(self):"""确认退出刷题"""reply = QMessageBox.question(self, "确认", "确定要退出当前刷题吗?", QMessageBox.Yes | QMessageBox.No)if reply == QMessageBox.Yes:self.practice_mode = Falseself.show_main_page()# 错题集相关方法def show_wrong_answers(self):"""显示错题"""self.show_wrong_questions()def update_wrong_list(self):"""更新错题列表"""bank_name = self.wrong_bank_combo.currentText()if not bank_name:return# 清空树形视图self.wrong_tree.clear()# 加载错题for question in self.wrong_questions.get(bank_name, []):q_type = question['type']q_text = question['question']answer = question['answer']user_answer = question.get('user_answer', '')item = QTreeWidgetItem(self.wrong_tree)item.setText(0, q_type)item.setText(1, q_text)item.setText(2, answer)item.setText(3, user_answer)def show_wrong_detail(self):"""显示错题详情"""selected_items = self.wrong_tree.selectedItems()if not selected_items:QMessageBox.warning(self, "错误", "请先选择一道错题!")returnitem = selected_items[0]q_type = item.text(0)q_text = item.text(1)answer = item.text(2)user_answer = item.text(3)# 找到原始题目bank_name = self.wrong_bank_combo.currentText()index = Nonefor q in self.wrong_questions.get(bank_name, []):if q['question'] == q_text and q['answer'] == answer and q.get('user_answer') == user_answer:index = q.get('index')breakif index is None or index >= len(self.current_questions):QMessageBox.warning(self, "错误", "无法找到原始题目!")returnoriginal_question = self.current_questions[index]dialog = QDialog(self)dialog.setWindowTitle("错题详情")dialog.setModal(True)dialog.resize(800, 600)layout = QVBoxLayout(dialog)# 题目类型type_label = QLabel(f"[{q_type}]")type_label.setStyleSheet("font-weight: bold; color: #3498db;")layout.addWidget(type_label)# 题目文本question_label = QLabel(q_text)question_label.setWordWrap(True)question_label.setStyleSheet("font-size: 15px;")layout.addWidget(question_label)# 选项options_group = QGroupBox("选项")options_layout = QVBoxLayout(options_group)for i, option in enumerate(original_question['options']):option_label = QLabel(f"{chr(65+i)}. {option}")options_layout.addWidget(option_label)layout.addWidget(options_group)# 正确答案correct_frame = QWidget()correct_layout = QHBoxLayout(correct_frame)correct_label = QLabel("正确答案:")correct_label.setStyleSheet("font-weight: bold; color: green;")answer_label = QLabel(answer)correct_layout.addWidget(correct_label)correct_layout.addWidget(answer_label)correct_layout.addStretch()layout.addWidget(correct_frame)# 你的答案user_frame = QWidget()user_layout = QHBoxLayout(user_frame)user_label = QLabel("你的答案:")user_label.setStyleSheet("font-weight: bold; color: red;")user_answer_label = QLabel(user_answer)user_layout.addWidget(user_label)user_layout.addWidget(user_answer_label)user_layout.addStretch()layout.addWidget(user_frame)# 解析(如果有)if 'explanation' in original_question:explanation_group = QGroupBox("解析")explanation_layout = QVBoxLayout(explanation_group)explanation_text = QTextEdit(original_question['explanation'])explanation_text.setReadOnly(True)explanation_layout.addWidget(explanation_text)layout.addWidget(explanation_group)# 关闭按钮close_btn = QPushButton("关闭")close_btn.clicked.connect(dialog.accept)layout.addWidget(close_btn, alignment=Qt.AlignRight)dialog.exec_()def delete_wrong_question(self):"""删除错题"""selected_items = self.wrong_tree.selectedItems()if not selected_items:QMessageBox.warning(self, "错误", "请先选择一道错题!")returnbank_name = self.wrong_bank_combo.currentText()if not bank_name or bank_name not in self.wrong_questions:returnreply = QMessageBox.question(self, "确认", "确定要删除这道错题吗?", QMessageBox.Yes | QMessageBox.No)if reply == QMessageBox.No:return# 获取选中的错题信息item = selected_items[0]q_text = item.text(1)answer = item.text(2)user_answer = item.text(3)# 从错题集中删除for i, question in enumerate(self.wrong_questions[bank_name]):if (question['question'] == q_text and question['answer'] == answer and question.get('user_answer') == user_answer):self.wrong_questions[bank_name].pop(i)break# 从树形视图中删除self.wrong_tree.takeTopLevelItem(self.wrong_tree.indexOfTopLevelItem(item))QMessageBox.information(self, "成功", "错题已删除!")def back_from_wrong_page(self):"""从错题集页面返回"""if hasattr(self, 'from_result_page') and self.from_result_page:self.content_area.setCurrentWidget(self.result_page)else:self.show_main_page()if __name__ == "__main__":app = QApplication(sys.argv)# 尝试设置应用图标,如果没有则忽略try:app.setWindowIcon(QIcon("icon.ico"))except:pass# 设置全局字体font = QFont()font.setFamily("Arial")font.setPointSize(10)app.setFont(font)window = BrainyQuiz()window.show()sys.exit(app.exec_())

项目结构

brainy-quiz/
├── main.py                # 主程序入口
├── question_banks/        # 默认题库目录
│   ├── sample.json        # 示例JSON题库
│   └── sample.xlsx        # 示例Excel题库
├── icons/                 # 图标资源
│   └── icon.ico           # 应用图标
└── README.md              # 项目说明文档

🚀 未来扩展与优化目标

  1. 功能扩展
    • 添加用户系统,支持多账户
    • 实现云端同步功能
    • 增加题目解析和知识点标签
  2. 性能优化
    • 使用数据库存储大规模题库
    • 添加题目缓存机制
    • 优化界面渲染性能
  3. 用户体验改进
    • 添加夜间模式
    • 支持自定义主题
    • 增加快捷键操作
  4. 教育功能增强
    • 实现智能出题算法
    • 添加学习进度跟踪
    • 支持题目收藏功能

💡 总结

白泽题库系统通过PyQt5实现了功能完备的桌面端刷题应用,具有以下优势:

  1. 跨平台性:基于Python和Qt,可在Windows、macOS和Linux上运行
  2. 易用性:直观的界面设计,降低学习成本
  3. 扩展性:模块化架构便于功能扩展
  4. 数据兼容:支持主流文件格式导入导出

通过本项目的开发,我们不仅掌握了PyQt5的基本使用方法,还深入理解了桌面应用开发的全流程。未来可以进一步探索:

  • 使用PyInstaller打包为独立可执行文件
  • 集成机器学习算法实现智能推荐
  • 开发移动端配套应用

希望本文能为Python GUI开发和在线教育系统设计提供有价值的参考!


版权声明:本文代码采用MIT开源协议,欢迎用于学习和非商业用途。商业使用请联系作者授权。

相关文章:

  • 【threejs】每天一个小案例讲解:常见几何体
  • FastAPI 教程:从入门到实践
  • 主键(PRIMARY KEY)与唯一键(UNIQUE KEY)的区别详解
  • React Native 导航系统实战(React Navigation)
  • OOM模拟排查过程记录
  • 解决helm Doris重启后由于root密码修改导致加入集群不成功的问题
  • DevSecOps实践:CI/CD流水线集成SAST工具详解
  • 理解 Spring Cloud Config:配置文件发现与命名规范
  • Python爬虫基础之Selenium详解
  • iOS26 深度解析:WWDC25 重磅系统的设计革新与争议焦点
  • 【iOS】cell的复用以及自定义cell
  • Vim 匹配跳转与搜索命令完整学习笔记
  • ios 26官宣:car play升级提升车载体验
  • React---day12
  • SpringBoot自动化部署实战
  • 生成xcframework
  • <7>-MySQL内置函数
  • 51c嵌入式※~电路~合集32~PWM
  • BERT情感分类
  • BERT 位置嵌入机制与代码解析
  • 网站开发也需要源码吗/40个免费网站推广平台
  • 长沙公司网站设计/关键词整站优化
  • 做图片网站赚钱吗/竞价广告是什么意思
  • 网站被挂马原因/怎么做品牌推广和宣传
  • 网站建设 视频/百度在线扫一扫
  • 网站建设和管理情况调查表/手机百度app下载安装