基于Python實現(xiàn)一個簡單的題庫與在線考試系統(tǒng)
概述
在當(dāng)今信息化教育時代,在線學(xué)習(xí)與考試系統(tǒng)已成為教育技術(shù)領(lǐng)域的重要組成部分。本文將詳細介紹一款名為"白澤題庫系統(tǒng)"的智能刷題軟件的設(shè)計與實現(xiàn),該系統(tǒng)基于Python的PyQt5框架開發(fā),集題庫管理、隨機組卷、智能刷題、錯題集和學(xué)習(xí)統(tǒng)計等功能于一體。
白澤是中國古代神話中的神獸,以博學(xué)多識著稱,本系統(tǒng)取其"智慧"之意,旨在為用戶提供一個高效、智能的刷題學(xué)習(xí)平臺。系統(tǒng)采用模塊化設(shè)計,具有良好的擴展性和用戶體驗,特別適合各類考試備考使用。
功能特點
白澤題庫系統(tǒng)具備以下核心功能模塊:
| 功能模塊 | 描述 | 技術(shù)實現(xiàn) |
|---|---|---|
| 題庫管理 | 支持創(chuàng)建、編輯、導(dǎo)入/導(dǎo)出題庫 | JSON/Excel文件處理 |
| 隨機組卷 | 按題型比例隨機生成試卷 | 隨機算法、計時功能 |
| 題海刷題 | 順序/逆序/隨機刷題模式 | 用戶答案記錄 |
| 錯題集 | 自動收集錯題,支持復(fù)習(xí) | 數(shù)據(jù)結(jié)構(gòu)存儲 |
| 學(xué)習(xí)統(tǒng)計 | 可視化錯題分布與趨勢 | Matplotlib圖表 |
系統(tǒng)采用MVC架構(gòu)設(shè)計,主要技術(shù)棧包括:
- 前端:PyQt5 + Qt Designer
- 數(shù)據(jù)處理:JSON + openpyxl
- 圖表展示:Matplotlib
- 核心邏輯:Python 3.x
界面展示
主界面

題庫管理


考試模式


刷題模式

錯題集

學(xué)習(xí)統(tǒng)計

系統(tǒng)架構(gòu)設(shè)計
類結(jié)構(gòu)圖

Excel題庫填寫格式模板

題庫題目填寫格式表
| 字段名 | 類型 | 必填 | 說明 | 示例 |
|---|---|---|---|---|
| type | 字符串 | 是 | 題目類型,可選值:“單選題”、“多選題”、“判斷題” | “單選題” |
| question | 字符串 | 是 | 題目內(nèi)容 | “Python是什么類型的語言?” |
| options | 字符串列表 | 單選/多選必填 | 題目選項(判斷題固定為[“正確”, “錯誤”]) | [“編譯型”, “解釋型”, “混合型”, “匯編型”] |
| answer | 字符串 | 是 | 正確答案(多選題用逗號+空格分隔) | “B”(單選) “A, B, C”(多選) “正確”(判斷) |
| score | 整數(shù) | 否 | 題目分值(默認1分) | 2 |
| explanation | 字符串 | 否 | 題目解析(可選) | “Python是解釋型語言,代碼在運行時逐行解釋執(zhí)行。” |
核心數(shù)據(jù)結(jié)構(gòu)
題庫存儲結(jié)構(gòu):
{
"題庫名稱": [
{
"type": "單選題",
"question": "問題文本",
"options": ["A", "B", "C", "D"],
"answer": "A",
"score": 2
},
# 更多題目...
]
}
錯題集結(jié)構(gòu):
{
"題庫名稱": [
{
"type": "單選題",
"question": "問題文本",
"options": ["A", "B", "C", "D"],
"answer": "A",
"user_answer": "B",
"score": 2
},
# 更多錯題...
]
}
實現(xiàn)步驟詳解
1. 環(huán)境配置
首先需要安裝必要的Python庫:
pip install pyqt5 openpyxl matplotlib
2. 主窗口初始化
class BrainyQuiz(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("白澤題庫系統(tǒng)")
self.setGeometry(100, 100, 1000, 700)
self.setStyleSheet(self.get_stylesheet())
# 初始化數(shù)據(jù)
self.current_question_bank = None
self.question_banks = {}
self.current_questions = []
self.current_index = 0
self.user_answers = {}
self.wrong_questions = {}
# 創(chuàng)建界面
self.init_ui()
self.load_question_bank_list()
3. 界面布局設(shè)計
系統(tǒng)采用側(cè)邊欄導(dǎo)航+堆疊窗口的設(shè)計模式:
def init_ui(self):
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
# 主布局:水平布局(側(cè)邊欄+內(nèi)容區(qū))
self.main_layout = QHBoxLayout(self.central_widget)
# 創(chuàng)建側(cè)邊欄
self.create_sidebar()
# 創(chuàng)建內(nèi)容區(qū)域(堆疊窗口)
self.content_area = QStackedWidget()
self.create_all_pages()
# 將組件添加到主布局
self.main_layout.addWidget(self.sidebar)
self.main_layout.addWidget(self.content_area)
4. 題庫管理實現(xiàn)
題庫支持JSON和Excel兩種格式的導(dǎo)入導(dǎo)出:
def import_question_bank(self):
filename, _ = QFileDialog.getOpenFileName(
self, "導(dǎo)入題庫", "", "題庫文件 (*.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):
# 計算各題型題目數(shù)量
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. 刷題模式實現(xiàn)
支持三種刷題順序:
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. 錯題集管理
自動記錄錯題并提供復(fù)習(xí)功能:
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_answer
self.wrong_questions[self.current_question_bank].append(wrong_question)
8. 學(xué)習(xí)統(tǒng)計可視化
使用Matplotlib生成錯題分析圖表:
def show_stats(self):
# 創(chuàng)建圖表
fig = self.stats_canvas.figure
fig.clear()
# 錯題題型分布餅圖
ax1 = fig.add_subplot(121)
ax1.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90)
# 各題庫錯題數(shù)量柱狀圖
ax2 = fig.add_subplot(122)
ax2.bar(bank_names, wrong_counts)
self.stats_canvas.draw()
關(guān)鍵代碼解析
1. 題目顯示邏輯
def show_question(self):
# 獲取當(dāng)前題目
current_question = self.current_questions[self.current_index]
# 顯示題目信息
self.question_text.setText(current_question['question'])
# 根據(jù)題型創(chuàng)建相應(yīng)選項
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_sorted
else:
return str(user_answer).upper() == str(correct_answer).upper()
3. 樣式表設(shè)計
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 FigureCanvas
class BrainyQuiz(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("白澤題庫系統(tǒng)")
self.setGeometry(100, 100, 1000, 700)
self.setStyleSheet(self.get_stylesheet())
# 初始化數(shù)據(jù)
self.current_question_bank = None
self.question_banks = {}
self.current_questions = []
self.current_index = 0
self.user_answers = {}
self.wrong_questions = {}
self.exam_mode = False
self.practice_mode = False
self.score = 0
self.total_score = 0
self.practice_order = "順序"
# 創(chuàng)建主界面
self.init_ui()
# 加載題庫列表
self.load_question_bank_list()
def get_stylesheet(self):
"""返回應(yīng)用程序樣式表"""
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)
# 左側(cè)導(dǎo)航欄
self.create_sidebar()
# 右側(cè)內(nèi)容區(qū)域
self.content_area = QStackedWidget()
# 添加各個頁面
self.create_all_pages()
# 將側(cè)邊欄和內(nèi)容區(qū)域添加到主布局
self.main_layout.addWidget(self.sidebar)
self.main_layout.addWidget(self.content_area)
def create_sidebar(self):
"""創(chuàng)建左側(cè)導(dǎo)航欄"""
self.sidebar = QWidget()
self.sidebar.setFixedWidth(200)
self.sidebar.setStyleSheet("background-color: #2c3e50;")
self.sidebar_layout = QVBoxLayout(self.sidebar)
# 應(yīng)用標(biāo)題
title = QLabel("?? 題庫考試系統(tǒng)")
title.setStyleSheet("""
QLabel {
color: white;
font-size: 18px;
font-weight: bold;
padding: 20px 10px;
}
""")
title.setAlignment(Qt.AlignCenter)
self.sidebar_layout.addWidget(title)
# 導(dǎo)航按鈕
self.create_navigation_buttons()
# 添加按鈕到側(cè)邊欄
self.sidebar_layout.addStretch()
self.sidebar_layout.addWidget(self.btn_exit)
def create_navigation_buttons(self):
"""創(chuàng)建導(dǎo)航按鈕"""
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("?? 學(xué)習(xí)統(tǒng)計")
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)
# 添加按鈕到側(cè)邊欄
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):
"""創(chuàng)建所有內(nèi)容頁面"""
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):
"""創(chuàng)建主頁面"""
page = QWidget()
layout = QVBoxLayout(page)
# 標(biāo)題
title = QLabel("白澤為引,點亮你的題海修行之路")
title.setStyleSheet("""
QLabel {
font-size: 24px;
font-weight: bold;
color: #2c3e50;
margin-bottom: 30px;
}
""")
title.setAlignment(Qt.AlignCenter)
layout.addWidget(title)
# 應(yīng)用描述
desc = QLabel("一款功能強大的刷題軟件,支持多種題型和智能學(xué)習(xí)統(tǒng)計")
desc.setStyleSheet("font-size: 16px; color: #7f8c8d;")
desc.setAlignment(Qt.AlignCenter)
layout.addWidget(desc)
# 插入空行
spacer = QLabel("")
spacer.setFixedHeight(15) # 設(shè)置高度,10~20px 之間都可以
layout.addWidget(spacer)
# 圖標(biāo)(如果沒有資源文件則不顯示)
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:
pass
layout.addStretch()
return page
def create_bank_manager_page(self):
"""創(chuàng)建題庫管理頁面"""
page = QWidget()
layout = QVBoxLayout(page)
# 標(biāo)題
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)
# 按鈕區(qū)域
btn_layout = QHBoxLayout()
self.btn_new_bank = QPushButton("? 新建題庫")
self.btn_new_bank.clicked.connect(self.create_new_bank)
self.btn_import_bank = QPushButton("?? 導(dǎo)入題庫")
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("?? 導(dǎo)出題庫")
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 page
def create_bank_editor_page(self):
"""創(chuàng)建題庫編輯器頁面"""
page = QWidget()
layout = QVBoxLayout(page)
# 標(biāo)題
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)
# 按鈕區(qū)域
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 page
def create_exam_setup_page(self):
"""創(chuàng)建組卷設(shè)置頁面"""
page = QWidget()
layout = QVBoxLayout(page)
# 標(biāo)題
self.exam_setup_title = QLabel("?? 隨機組卷設(shè)置")
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)
# 設(shè)置表單
form_layout = QFormLayout()
# 題庫選擇
self.bank_combo = QComboBox()
form_layout.addRow("選擇題庫:", self.bank_combo)
# 組卷設(shè)置
self.exam_settings_group = QGroupBox("組卷設(shè)置")
exam_layout = QVBoxLayout(self.exam_settings_group)
# 題目數(shù)量
self.num_questions = QLineEdit("10")
self.num_questions.setValidator(QtGui.QIntValidator(1, 100))
exam_layout.addWidget(QLabel("題目數(shù)量:"))
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)
# 按鈕區(qū)域
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 page
def create_exam_page(self):
"""創(chuàng)建考試頁面"""
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)
# 題目內(nèi)容
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)
# 按鈕區(qū)域
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 page
def create_result_page(self):
"""創(chuàng)建測驗結(jié)果頁面"""
page = QWidget()
layout = QVBoxLayout(page)
# 標(biāo)題
title = QLabel("?? 測驗結(jié)果")
title.setStyleSheet("""
QLabel {
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
}
""")
title.setAlignment(Qt.AlignCenter)
layout.addWidget(title)
# 結(jié)果統(tǒng)計
self.stats_group = QGroupBox("測驗統(tǒng)計")
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)
# 按鈕區(qū)域
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 page
def create_wrong_questions_page(self):
"""創(chuàng)建錯題集頁面"""
page = QWidget()
layout = QVBoxLayout(page)
# 標(biāo)題
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)
# 按鈕區(qū)域
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 page
def create_stats_page(self):
"""創(chuàng)建學(xué)習(xí)統(tǒng)計頁面"""
page = QWidget()
layout = QVBoxLayout(page)
# 標(biāo)題
title = QLabel("?? 學(xué)習(xí)統(tǒng)計")
title.setStyleSheet("""
QLabel {
font-size: 20px;
font-weight: bold;
margin-bottom: 20px;
}
""")
title.setAlignment(Qt.AlignCenter)
layout.addWidget(title)
# 圖表區(qū)域
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 page
def create_practice_setup_page(self):
"""創(chuàng)建刷題設(shè)置頁面"""
page = QWidget()
layout = QVBoxLayout(page)
# 標(biāo)題
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)
# 按鈕區(qū)域
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 page
def create_practice_page(self):
"""創(chuàng)建刷題頁面"""
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)
# 題目內(nèi)容
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)
# 按鈕區(qū)域
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
# 頁面導(dǎo)航方法
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_name
self.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):
"""顯示組卷設(shè)置頁面"""
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):
"""顯示刷題設(shè)置頁面"""
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):
"""顯示當(dāng)前題目"""
if not self.current_questions:
QMessageBox.warning(self, "錯誤", "沒有可用的題目!")
return
self.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)}")
# 獲取當(dāng)前題目
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)
# 更新按鈕狀態(tài)
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, "錯誤", "沒有可用的題目!")
return
self.content_area.setCurrentWidget(self.practice_page)
# 更新進度
self.practice_progress_label.setText(f"題目 {self.current_index + 1}/{len(self.current_questions)}")
self.answer_feedback.clear()
# 獲取當(dāng)前題目
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)
# 更新按鈕狀態(tài)
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):
"""顯示測驗結(jié)果"""
total_questions = len(self.current_questions)
accuracy = (correct_count / total_questions) * 100 if total_questions > 0 else 0
self.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, "提示", "錯題集為空!")
return
self.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):
"""顯示學(xué)習(xí)統(tǒng)計頁面"""
if not self.wrong_questions:
QMessageBox.information(self, "提示", "暫無統(tǒng)計數(shù)據(jù)!")
return
# 創(chuàng)建圖表
fig = self.stats_canvas.figure
fig.clear()
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
# 設(shè)置中文字體
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用來正常顯示中文標(biāo)簽
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] += 1
labels = list(type_counts.keys())
sizes = list(type_counts.values())
ax1.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90)
ax1.set_title('錯題題型分布')
# 各題庫錯題數(shù)量
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('各題庫錯題數(shù)量')
ax2.set_ylabel('數(shù)量')
ax2.tick_params(axis='x', rotation=45)
fig.tight_layout()
self.stats_canvas.draw()
self.content_area.setCurrentWidget(self.stats_page)
# 題庫管理相關(guān)方法
def import_question_bank(self):
"""導(dǎo)入題庫文件"""
filename, _ = QFileDialog.getOpenFileName(
self,
"導(dǎo)入題庫",
"",
"題庫文件 (*.json *.xlsx)"
)
if not filename:
return
try:
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}' 導(dǎo)入成功!")
self.show_bank_manager()
except Exception as e:
QMessageBox.warning(self, "錯誤", f"導(dǎo)入失敗: {str(e)}")
def create_new_bank(self):
"""創(chuàng)建新題庫"""
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}' 創(chuàng)建成功!")
def load_selected_bank(self):
"""加載選中的題庫"""
selected_items = self.bank_list.selectedItems()
if not selected_items:
QMessageBox.warning(self, "錯誤", "請先選擇一個題庫!")
return
bank_name = selected_items[0].text()
self.current_question_bank = bank_name
QMessageBox.information(self, "成功", f"題庫 '{bank_name}' 已加載!")
def edit_selected_bank(self):
"""編輯選中的題庫"""
selected_items = self.bank_list.selectedItems()
if not selected_items:
QMessageBox.warning(self, "錯誤", "請先選擇一個題庫!")
return
bank_name = selected_items[0].text()
self.show_bank_editor(bank_name)
def export_selected_bank(self):
"""導(dǎo)出選中的題庫"""
selected_items = self.bank_list.selectedItems()
if not selected_items:
QMessageBox.warning(self, "錯誤", "請先選擇一個題庫!")
return
bank_name = selected_items[0].text()
# 選擇導(dǎo)出格式
formats = ["JSON (.json)", "Excel (.xlsx)"]
format_choice, ok = QInputDialog.getItem(
self, "選擇導(dǎo)出格式", "請選擇導(dǎo)出格式:", 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, "錯誤", "請先選擇一個題庫!")
return
bank_name = selected_items[0].text()
reply = QMessageBox.question(
self,
"確認",
f"確定要刪除題庫 '{bank_name}' 嗎?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
# 從內(nèi)存中刪除
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:
return
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(["單選題", "多選題", "判斷題"])
type_layout.addWidget(q_type_combo)
layout.addLayout(type_layout)
# 題目內(nèi)容
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, "錯誤", "題目不能為空!")
return
if 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, "錯誤", "請設(shè)置正確答案!")
return
try:
score = int(score_edit.text())
except ValueError:
score = 1
# 創(chuàng)建題目字典
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, "錯誤", "請先選擇要編輯的題目!")
return
item = 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)
# 題目內(nèi)容
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(["正確", "錯誤"])
# 設(shè)置當(dāng)前答案
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, "錯誤", "題目不能為空!")
return
if 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, "錯誤", "請設(shè)置正確答案!")
return
try:
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, "錯誤", "請先選擇要刪除的題目!")
return
reply = 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_bank
if 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,
"導(dǎo)出題庫",
f"{bank_name}.xlsx",
"Excel 文件 (*.xlsx)"
)
if filename:
try:
self.export_to_excel(bank_name, filename)
QMessageBox.information(self, "成功", f"題庫 '{bank_name}' 已導(dǎo)出為Excel格式!")
except Exception as e:
QMessageBox.warning(self, "錯誤", f"導(dǎo)出失敗: {e}")
def export_to_excel(self, bank_name, filename):
"""將題庫導(dǎo)出為Excel文件"""
workbook = openpyxl.Workbook()
sheet = workbook.active
sheet.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的數(shù)據(jù)類型?",
"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 os
files = []
for file in os.listdir('.'):
if file.endswith(extension):
files.append(file)
return files
def load_excel_bank(self, file_path):
"""從Excel文件加載題庫"""
workbook = openpyxl.load_workbook(file_path)
sheet = workbook.active
questions = []
for row in sheet.iter_rows(min_row=2, values_only=True):
if not row[0]: # 跳過空行
continue
q_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
# 考試相關(guān)方法
def start_exam(self):
"""開始考試"""
bank_name = self.bank_combo.currentText()
if not bank_name:
QMessageBox.warning(self, "錯誤", "請選擇題庫!")
return
# 加載題庫
self.current_question_bank = bank_name
questions = self.question_banks[bank_name]
if not questions:
QMessageBox.warning(self, "錯誤", "題庫中沒有題目!")
return
try:
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%!")
return
except ValueError:
QMessageBox.warning(self, "錯誤", "請輸入有效的數(shù)字!")
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'] == "判斷題"]
# 計算各題型題目數(shù)量
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)
# 設(shè)置考試計時器
try:
time_limit = int(self.time_limit.text())
self.exam_mode = True
self.exam_start_time = datetime.now()
self.exam_time_limit = time_limit * 60 # 轉(zhuǎn)換為秒
except ValueError:
QMessageBox.warning(self, "錯誤", "請輸入有效的時間限制!")
return
# 設(shè)置當(dāng)前題目
self.current_questions = selected_questions
self.current_index = 0
self.user_answers = {}
self.score = 0
# 顯示題目
self.show_question()
def update_exam_timer(self):
"""更新考試計時器"""
if not self.exam_mode:
return
elapsed = (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 -= 1
self.show_question()
def next_question(self):
"""顯示下一題"""
self.save_current_answer()
if self.current_index < len(self.current_questions) - 1:
self.current_index += 1
self.show_question()
else:
self.submit_exam()
def save_current_answer(self):
"""保存當(dāng)前題目的答案"""
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)
break
elif 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 = 0
self.score = 0
for 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_sorted
else:
is_correct = str(user_answer).upper() == str(correct_answer).upper()
if is_correct:
self.score += q_score
correct_count += 1
else:
# 添加到錯題集
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_answer
wrong_question['index'] = i
self.wrong_questions[self.current_question_bank].append(wrong_question)
# 顯示結(jié)果
self.show_exam_result(correct_count)
def retry_exam(self):
"""重新測驗"""
self.start_exam()
def confirm_exit_exam(self):
"""確認退出考試"""
reply = QMessageBox.question(
self,
"確認",
"確定要退出當(dāng)前考試嗎? 所有進度將丟失!",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
self.show_main_page()
# 刷題相關(guān)方法
def start_practice(self):
"""開始刷題"""
bank_name = self.practice_bank_combo.currentText()
if not bank_name:
QMessageBox.warning(self, "錯誤", "請選擇題庫!")
return
# 加載題庫
self.current_question_bank = bank_name
questions = self.question_banks[bank_name]
if not questions:
QMessageBox.warning(self, "錯誤", "題庫中沒有題目!")
return
# 設(shè)置刷題順序
self.practice_order = self.order_combo.currentText()
# 準(zhǔn)備題目
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 = 0
self.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_sorted
else:
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_answer
self.wrong_questions[self.current_question_bank].append(wrong_question)
self.btn_submit_practice.setEnabled(False)
def save_current_practice_answer(self):
"""保存當(dāng)前刷題答案"""
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)
break
elif 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 -= 1
self.show_practice_question()
def next_practice_question(self):
"""下一題"""
if self.current_index < len(self.current_questions) - 1:
self.current_index += 1
self.show_practice_question()
def confirm_exit_practice(self):
"""確認退出刷題"""
reply = QMessageBox.question(
self,
"確認",
"確定要退出當(dāng)前刷題嗎?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
self.practice_mode = False
self.show_main_page()
# 錯題集相關(guān)方法
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, "錯誤", "請先選擇一道錯題!")
return
item = 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 = None
for 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')
break
if index is None or index >= len(self.current_questions):
QMessageBox.warning(self, "錯誤", "無法找到原始題目!")
return
original_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)
# 關(guān)閉按鈕
close_btn = QPushButton("關(guān)閉")
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, "錯誤", "請先選擇一道錯題!")
return
bank_name = self.wrong_bank_combo.currentText()
if not bank_name or bank_name not in self.wrong_questions:
return
reply = 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)
# 嘗試設(shè)置應(yīng)用圖標(biāo),如果沒有則忽略
try:
app.setWindowIcon(QIcon("icon.ico"))
except:
pass
# 設(shè)置全局字體
font = QFont()
font.setFamily("Arial")
font.setPointSize(10)
app.setFont(font)
window = BrainyQuiz()
window.show()
sys.exit(app.exec_())
項目結(jié)構(gòu)
brainy-quiz/
├── main.py # 主程序入口
├── question_banks/ # 默認題庫目錄
│ ├── sample.json # 示例JSON題庫
│ └── sample.xlsx # 示例Excel題庫
├── icons/ # 圖標(biāo)資源
│ └── icon.ico # 應(yīng)用圖標(biāo)
└── README.md # 項目說明文檔
未來擴展與優(yōu)化目標(biāo)
1.功能擴展:
添加用戶系統(tǒng),支持多賬戶
實現(xiàn)云端同步功能
增加題目解析和知識點標(biāo)簽
2.性能優(yōu)化:
使用數(shù)據(jù)庫存儲大規(guī)模題庫
添加題目緩存機制
優(yōu)化界面渲染性能
3.用戶體驗改進:
添加夜間模式
支持自定義主題
增加快捷鍵操作
4.教育功能增強:
實現(xiàn)智能出題算法
添加學(xué)習(xí)進度跟蹤
支持題目收藏功能
總結(jié)
白澤題庫系統(tǒng)通過PyQt5實現(xiàn)了功能完備的桌面端刷題應(yīng)用,具有以下優(yōu)勢:
- 跨平臺性:基于Python和Qt,可在Windows、macOS和Linux上運行
- 易用性:直觀的界面設(shè)計,降低學(xué)習(xí)成本
- 擴展性:模塊化架構(gòu)便于功能擴展
- 數(shù)據(jù)兼容:支持主流文件格式導(dǎo)入導(dǎo)出
通過本項目的開發(fā),我們不僅掌握了PyQt5的基本使用方法,還深入理解了桌面應(yīng)用開發(fā)的全流程。未來可以進一步探索:
- 使用PyInstaller打包為獨立可執(zhí)行文件
- 集成機器學(xué)習(xí)算法實現(xiàn)智能推薦
- 開發(fā)移動端配套應(yīng)用
以上就是基于Python實現(xiàn)一個簡單的題庫與在線考試系統(tǒng)的詳細內(nèi)容,更多關(guān)于Python在線考試系統(tǒng)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python?pywin32實現(xiàn)word與Excel的處理
這篇文章主要介紹了Python?pywin32實現(xiàn)word與Excel的處理,pywin32處理Word大多數(shù)用于格式轉(zhuǎn)換,因為一般讀寫操作都可以借助python-docx實現(xiàn),除非真的有特殊要求,但大部分企業(yè)對Wrod操作不會有太多復(fù)雜需求2022-08-08
keras自定義回調(diào)函數(shù)查看訓(xùn)練的loss和accuracy方式
這篇文章主要介紹了keras自定義回調(diào)函數(shù)查看訓(xùn)練的loss和accuracy方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-05-05
Python Dict找出value大于某值或key大于某值的所有項方式
這篇文章主要介紹了Python Dict找出value大于某值或key大于某值的所有項方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-06-06
使用Python處理非標(biāo)準(zhǔn)JSON的實用方案
JSON(JavaScript Object Notation)作為輕量級數(shù)據(jù)交換格式被廣泛使用,但實際開發(fā)中常遇到不符合標(biāo)準(zhǔn)規(guī)范的JSON數(shù)據(jù),本文將探討如何用Python處理這些特殊情況,并提供實用解決方案,需要的朋友可以參考下2025-09-09
詳解Python編程中基本的數(shù)學(xué)計算使用
這篇文章主要介紹了Python編程中基本的數(shù)學(xué)計算使用,其中重點講了除法運算及相關(guān)division模塊的使用,需要的朋友可以參考下2016-02-02

