软工博客28
关于创建标准化考试系统:
import json
import time
import os
import sqlite3
import shutil
from datetime import datetime
from tkinter import *
from tkinter import ttk, messagebox, filedialog
from PIL import Image, ImageTk
数据库操作类
class ExamDatabase:
def init(self, db_file="exam_system.db"):
self.db_file = db_file
self.conn = None
self.connect()
self.create_tables()
def connect(self):
    """连接到数据库"""
    self.conn = sqlite3.connect(self.db_file)
    self.conn.row_factory = sqlite3.Row
def create_tables(self):
    """创建数据库表"""
    cursor = self.conn.cursor()
    
    # 考试表
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS exams (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL UNIQUE,
        duration INTEGER NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
    ''')
    
    # 问题表
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS questions (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        exam_id INTEGER NOT NULL,
        question_text TEXT NOT NULL,
        question_type TEXT NOT NULL,
        options TEXT,  -- JSON格式存储选项
        correct_answer TEXT NOT NULL,
        FOREIGN KEY (exam_id) REFERENCES exams(id) ON DELETE CASCADE
    )
    ''')
    
    # 学生答案表
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS student_answers (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        exam_record_id INTEGER NOT NULL,
        question_id INTEGER NOT NULL,
        answer TEXT,
        is_correct INTEGER DEFAULT 0,
        answered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (exam_record_id) REFERENCES exam_records(id) ON DELETE CASCADE,
        FOREIGN KEY (question_id) REFERENCES questions(id) ON DELETE CASCADE
    )
    ''')
    
    # 考试记录表
    cursor.execute('''
    CREATE TABLE IF NOT EXISTS exam_records (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        exam_id INTEGER NOT NULL,
        student_name TEXT,
        start_time TIMESTAMP,
        end_time TIMESTAMP,
        score INTEGER,
        total_questions INTEGER,
        FOREIGN KEY (exam_id) REFERENCES exams(id) ON DELETE CASCADE
    )
    ''')
    
    self.conn.commit()
def add_exam(self, title, duration):
    """添加新考试"""
    try:
        cursor = self.conn.cursor()
        cursor.execute("INSERT INTO exams (title, duration) VALUES (?, ?)", 
                     (title, duration))
        self.conn.commit()
        return cursor.lastrowid
    except sqlite3.IntegrityError:
        return None
def get_exam(self, exam_id=None, title=None):
    """获取考试信息"""
    cursor = self.conn.cursor()
    if exam_id:
        cursor.execute("SELECT * FROM exams WHERE id=?", (exam_id,))
    elif title:
        cursor.execute("SELECT * FROM exams WHERE title=?", (title,))
    else:
        return None
    return cursor.fetchone()
def get_all_exams(self):
    """获取所有考试"""
    cursor = self.conn.cursor()
    cursor.execute("SELECT * FROM exams ORDER BY created_at DESC")
    return cursor.fetchall()
def add_question(self, exam_id, question_text, question_type, options, correct_answer):
    """添加问题"""
    cursor = self.conn.cursor()
    options_json = json.dumps(options) if options else None
    cursor.execute(
        "INSERT INTO questions (exam_id, question_text, question_type, options, correct_answer) VALUES (?, ?, ?, ?, ?)",
        (exam_id, question_text, question_type, options_json, correct_answer)
    )
    self.conn.commit()
    return cursor.lastrowid
def get_questions(self, exam_id):
    """获取考试的所有问题"""
    cursor = self.conn.cursor()
    cursor.execute("SELECT * FROM questions WHERE exam_id=? ORDER BY id", (exam_id,))
    return cursor.fetchall()
def start_exam(self, exam_id, student_name):
    """开始考试并创建记录"""
    cursor = self.conn.cursor()
    start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    cursor.execute(
        "INSERT INTO exam_records (exam_id, student_name, start_time) VALUES (?, ?, ?)",
        (exam_id, student_name, start_time)
    )
    self.conn.commit()
    return cursor.lastrowid
def submit_answer(self, record_id, question_id, answer, is_correct):
    """提交答案"""
    cursor = self.conn.cursor()
    # 检查是否已有答案,避免重复
    cursor.execute(
        "SELECT id FROM student_answers WHERE exam_record_id=? AND question_id=?",
        (record_id, question_id)
    )
    existing = cursor.fetchone()
    
    if existing:
        cursor.execute(
            "UPDATE student_answers SET answer=?, is_correct=? WHERE id=?",
            (answer, is_correct, existing['id'])
        )
    else:
        cursor.execute(
            "INSERT INTO student_answers (exam_record_id, question_id, answer, is_correct) VALUES (?, ?, ?, ?)",
            (record_id, question_id, answer, is_correct)
        )
    self.conn.commit()
def finish_exam(self, record_id, score, total_questions):
    """完成考试并记录分数"""
    cursor = self.conn.cursor()
    end_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    cursor.execute(
        "UPDATE exam_records SET end_time=?, score=?, total_questions=? WHERE id=?",
        (end_time, score, total_questions, record_id)
    )
    self.conn.commit()
def get_exam_results(self, record_id):
    """获取考试结果"""
    cursor = self.conn.cursor()
    cursor.execute(
        "SELECT er.*, e.title, e.duration FROM exam_records er JOIN exams e ON er.exam_id=e.id WHERE er.id=?",
        (record_id,)
    )
    record = cursor.fetchone()
    
    if record:
        cursor.execute(
            "SELECT q.*, sa.answer, sa.is_correct FROM student_answers sa "
            "JOIN questions q ON sa.question_id=q.id "
            "WHERE sa.exam_record_id=? ORDER BY q.id",
            (record_id,)
        )
        questions = cursor.fetchall()
        return record, questions
    return None, None
def get_all_results(self):
    """获取所有考试结果"""
    cursor = self.conn.cursor()
    cursor.execute(
        "SELECT er.id, e.title, er.student_name, er.score, er.total_questions, "
        "er.start_time, er.end_time FROM exam_records er JOIN exams e ON er.exam_id=e.id "
        "WHERE er.end_time IS NOT NULL ORDER BY er.end_time DESC"
    )
    return cursor.fetchall()
def delete_exam(self, exam_id):
    """删除考试及关联数据"""
    cursor = self.conn.cursor()
    cursor.execute("DELETE FROM exams WHERE id=?", (exam_id,))
    self.conn.commit()
def close(self):
    """关闭数据库连接"""
    if self.conn:
        self.conn.close()
考试系统核心逻辑
class ExamSystem:
def init(self, db):
self.db = db
self.current_exam = None
self.current_record = None
self.student_name = None
def create_exam(self, title, duration):
    """创建新考试"""
    return self.db.add_exam(title, duration)
def add_question(self, exam_id, question_text, question_type, options, correct_answer):
    """添加问题到考试"""
    return self.db.add_question(exam_id, question_text, question_type, options, correct_answer)
def get_exam_list(self):
    """获取考试列表"""
    return self.db.get_all_exams()
def start_exam(self, exam_id, student_name):
    """开始考试"""
    self.current_exam = self.db.get_exam(exam_id=exam_id)
    self.student_name = student_name
    self.current_record = self.db.start_exam(exam_id, student_name)
    return self.current_record
def get_exam_questions(self, exam_id):
    """获取考试问题"""
    return self.db.get_questions(exam_id)
def submit_answer(self, question_id, answer, is_correct):
    """提交答案"""
    if self.current_record:
        self.db.submit_answer(self.current_record, question_id, answer, is_correct)
def finish_exam(self, score, total_questions):
    """完成考试"""
    if self.current_record:
        self.db.finish_exam(self.current_record, score, total_questions)
        self.current_exam = None
        self.current_record = None
def get_exam_result(self, record_id):
    """获取考试结果"""
    return self.db.get_exam_results(record_id)
def get_all_results(self):
    """获取所有考试结果"""
    return self.db.get_all_results()
图形界面
class ExamSystemGUI:
def init(self, root):
self.root = root
self.root.title("标准化考试系统")
self.root.geometry("1000x700")
    # 初始化数据库和系统
    self.db = ExamDatabase()
    self.system = ExamSystem(self.db)
    
    # 设置样式
    self.setup_styles()
    
    # 创建主界面
    self.create_main_frame()
    
    # 加载图标
    self.load_icons()
    
    # 显示主菜单
    self.show_main_menu()
def setup_styles(self):
    """设置界面样式"""
    style = ttk.Style()
    style.configure("TFrame", background="#f0f0f0")
    style.configure("TButton", font=('Microsoft YaHei', 10), padding=5)
    style.configure("Title.TLabel", font=('Microsoft YaHei', 16, 'bold'), background="#f0f0f0")
    style.configure("Header.TLabel", font=('Microsoft YaHei', 12, 'bold'), background="#f0f0f0")
    style.configure("Normal.TLabel", font=('Microsoft YaHei', 10), background="#f0f0f0")
    style.configure("Question.TLabel", font=('Microsoft YaHei', 11), background="#f0f0f0", wraplength=600)
    style.configure("Option.TRadiobutton", font=('Microsoft YaHei', 10), background="#f0f0f0")
    style.configure("TEntry", font=('Microsoft YaHei', 10))
    style.configure("TCombobox", font=('Microsoft YaHei', 10))
def load_icons(self):
    """加载图标资源"""
    try:
        self.icons = {
            "exam": self.load_image("exam_icon.png", (64, 64)),
            "question": self.load_image("question_icon.png", (64, 64)),
            "result": self.load_image("result_icon.png", (64, 64)),
            "back": self.load_image("back_icon.png", (32, 32)),
            "next": self.load_image("next_icon.png", (32, 32)),
            "submit": self.load_image("submit_icon.png", (32, 32))
        }
    except:
        # 如果图标加载失败,使用文本代替
        self.icons = None
def load_image(self, filename, size):
    """加载并调整图片大小"""
    img = Image.open(filename)
    img = img.resize(size, Image.Resampling.LANCZOS)
    return ImageTk.PhotoImage(img)
def create_main_frame(self):
    """创建主框架"""
    self.main_frame = ttk.Frame(self.root)
    self.main_frame.pack(fill=BOTH, expand=True, padx=20, pady=20)
    
    # 标题区域
    self.title_frame = ttk.Frame(self.main_frame)
    self.title_frame.pack(fill=X, pady=(0, 20))
    
    self.title_label = ttk.Label(self.title_frame, text="标准化考试系统", style="Title.TLabel")
    self.title_label.pack(side=LEFT)
    
    # 内容区域
    self.content_frame = ttk.Frame(self.main_frame)
    self.content_frame.pack(fill=BOTH, expand=True)
    
    # 状态栏
    self.status_var = StringVar()
    self.status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=SUNKEN, anchor=W)
    self.status_bar.pack(side=BOTTOM, fill=X)
def clear_content(self):
    """清除内容区域"""
    for widget in self.content_frame.winfo_children():
        widget.destroy()
def show_main_menu(self):
    """显示主菜单"""
    self.clear_content()
    self.status_var.set("就绪")
    
    # 创建菜单按钮
    menu_frame = ttk.Frame(self.content_frame)
    menu_frame.pack(expand=True, pady=50)
    
    # 教师功能
    teacher_frame = ttk.LabelFrame(menu_frame, text="教师功能", padding=10)
    teacher_frame.grid(row=0, column=0, padx=20, pady=10, sticky="nsew")
    
    ttk.Button(teacher_frame, text="创建新考试", 
              command=self.show_create_exam, width=20).pack(pady=5)
    ttk.Button(teacher_frame, text="管理考试题目", 
              command=self.show_manage_exams, width=20).pack(pady=5)
    
    # 学生功能
    student_frame = ttk.LabelFrame(menu_frame, text="学生功能", padding=10)
    student_frame.grid(row=0, column=1, padx=20, pady=10, sticky="nsew")
    
    ttk.Button(student_frame, text="参加考试", 
              command=self.show_take_exam, width=20).pack(pady=5)
    ttk.Button(student_frame, text="查看考试结果", 
              command=self.show_exam_results, width=20).pack(pady=5)
    
    # 系统功能
    sys_frame = ttk.LabelFrame(menu_frame, text="系统功能", padding=10)
    sys_frame.grid(row=0, column=2, padx=20, pady=10, sticky="nsew")
    
    ttk.Button(sys_frame, text="数据备份", 
              command=self.backup_data, width=20).pack(pady=5)
    ttk.Button(sys_frame, text="退出系统", 
              command=self.root.quit, width=20).pack(pady=5)
    
    menu_frame.columnconfigure(0, weight=1)
    menu_frame.columnconfigure(1, weight=1)
    menu_frame.columnconfigure(2, weight=1)
def show_create_exam(self):
    """显示创建考试界面"""
    self.clear_content()
    self.status_var.set("创建新考试")
    
    # 返回按钮
    ttk.Button(self.content_frame, text="返回", command=self.show_main_menu).pack(anchor=NW, pady=5)
    
    # 表单框架
    form_frame = ttk.Frame(self.content_frame)
    form_frame.pack(fill=BOTH, expand=True, padx=50, pady=20)
    
    # 考试标题
    ttk.Label(form_frame, text="考试标题:").grid(row=0, column=0, sticky=W, pady=5)
    self.exam_title_entry = ttk.Entry(form_frame, width=40)
    self.exam_title_entry.grid(row=0, column=1, sticky=W, pady=5)
    
    # 考试时长
    ttk.Label(form_frame, text="考试时长(分钟):").grid(row=1, column=0, sticky=W, pady=5)
    self.exam_duration_spinbox = Spinbox(form_frame, from_=1, to=300, width=5)
    self.exam_duration_spinbox.grid(row=1, column=1, sticky=W, pady=5)
    
    # 创建按钮
    ttk.Button(form_frame, text="创建考试", command=self.create_exam).grid(row=2, column=1, sticky=E, pady=20)
def create_exam(self):
    """创建新考试"""
    title = self.exam_title_entry.get().strip()
    duration = self.exam_duration_spinbox.get()
    
    if not title:
        messagebox.showerror("错误", "请输入考试标题!")
        return
    
    try:
        duration = int(duration)
        if duration <= 0:
            raise ValueError
    except ValueError:
        messagebox.showerror("错误", "请输入有效的考试时长!")
        return
    
    exam_id = self.system.create_exam(title, duration)
    if exam_id:
        messagebox.showinfo("成功", f"考试 '{title}' 创建成功!")
        self.show_add_questions(exam_id, title)
    else:
        messagebox.showerror("错误", f"考试 '{title}' 已存在!")
def show_add_questions(self, exam_id, exam_title):
    """显示添加问题界面"""
    self.clear_content()
    self.status_var.set(f"添加问题 - {exam_title}")
    
    # 当前问题列表
    self.questions = []
    self.current_question = 0
    
    # 返回按钮
    ttk.Button(self.content_frame, text="返回", command=self.show_main_menu).pack(anchor=NW, pady=5)
    
    # 标题
    ttk.Label(self.content_frame, text=f"为考试 '{exam_title}' 添加问题", 
             style="Header.TLabel").pack(pady=10)
    
    # 问题表单框架
    self.question_form_frame = ttk.Frame(self.content_frame)
    self.question_form_frame.pack(fill=BOTH, expand=True, padx=50, pady=10)
    
    # 问题类型
    ttk.Label(self.question_form_frame, text="问题类型:").grid(row=0, column=0, sticky=W, pady=5)
    self.question_type_var = StringVar(value="single")
    ttk.Combobox(self.question_form_frame, textvariable=self.question_type_var, 
                values=["single", "multiple", "essay"], state="readonly", width=10).grid(row=0, column=1, sticky=W, pady=5)
    
    # 问题文本
    ttk.Label(self.question_form_frame, text="问题内容:").grid(row=1, column=0, sticky=NW, pady=5)
    self.question_text_entry = Text(self.question_form_frame, width=50, height=3, wrap=WORD)
    self.question_text_entry.grid(row=1, column=1, sticky=W, pady=5)
    
    # 选项框架 (仅对选择题显示)
    self.options_frame = ttk.LabelFrame(self.question_form_frame, text="选项", padding=10)
    self.options_frame.grid(row=2, column=0, columnspan=2, sticky="we", pady=5)
    
    # 动态添加选项控件
    self.option_entries = []
    self.add_option_entry()
    
    # 添加选项按钮
    ttk.Button(self.options_frame, text="添加选项", command=self.add_option_entry).pack(side=LEFT, padx=5)
    
    # 正确答案
    ttk.Label(self.question_form_frame, text="正确答案:").grid(row=3, column=0, sticky=W, pady=5)
    self.correct_answer_entry = ttk.Entry(self.question_form_frame, width=40)
    self.correct_answer_entry.grid(row=3, column=1, sticky=W, pady=5)
    
    # 按钮框架
    button_frame = ttk.Frame(self.question_form_frame)
    button_frame.grid(row=4, column=0, columnspan=2, pady=20)
    
    ttk.Button(button_frame, text="保存问题", command=lambda: self.save_question(exam_id)).pack(side=LEFT, padx=10)
    ttk.Button(button_frame, text="完成", command=self.show_main_menu).pack(side=LEFT, padx=10)
    
    # 绑定问题类型变化事件
    self.question_type_var.trace_add("write", self.update_question_form)
def add_option_entry(self):
    """添加一个选项输入框"""
    frame = ttk.Frame(self.options_frame)
    frame.pack(fill=X, pady=2)
    
    var = StringVar()
    entry = ttk.Entry(frame, textvariable=var, width=40)
    entry.pack(side=LEFT, padx=5)
    
    # 删除按钮
    ttk.Button(frame, text="×", width=2, command=lambda: frame.destroy()).pack(side=LEFT)
    
    self.option_entries.append((var, entry))
def update_question_form(self, *args):
    """根据问题类型更新表单"""
    q_type = self.question_type_var.get()
    
    if q_type in ["single", "multiple"]:
        self.options_frame.grid()
    else:
        self.options_frame.grid_remove()
def save_question(self, exam_id):
    """保存问题到数据库"""
    q_type = self.question_type_var.get()
    q_text = self.question_text_entry.get("1.0", END).strip()
    correct_answer = self.correct_answer_entry.get().strip()
    
    if not q_text:
        messagebox.showerror("错误", "请输入问题内容!")
        return
    
    options = []
    if q_type in ["single", "multiple"]:
        for var, entry in self.option_entries:
            option_text = var.get().strip()
            if option_text:
                options.append(option_text)
        
        if not options:
            messagebox.showerror("错误", "请至少添加一个选项!")
            return
        
        if not correct_answer:
            messagebox.showerror("错误", "请输入正确答案!")
            return
    
    # 保存到数据库
    question_id = self.system.add_question(exam_id, q_text, q_type, options, correct_answer)
    if question_id:
        messagebox.showinfo("成功", "问题添加成功!")
        self.clear_question_form()
    else:
        messagebox.showerror("错误", "问题添加失败!")
def clear_question_form(self):
    """清除问题表单"""
    self.question_text_entry.delete("1.0", END)
    self.correct_answer_entry.delete(0, END)
    
    # 清除所有选项
    for child in self.options_frame.winfo_children():
        child.destroy()
    
    # 重新添加一个空选项
    self.option_entries = []
    self.add_option_entry()
def show_manage_exams(self):
    """显示管理考试界面"""
    self.clear_content()
    self.status_var.set("管理考试")
    
    # 返回按钮
    ttk.Button(self.content_frame, text="返回", command=self.show_main_menu).pack(anchor=NW, pady=5)
    
    # 标题
    ttk.Label(self.content_frame, text="管理考试", style="Header.TLabel").pack(pady=10)
    
    # 考试列表
    exams = self.system.get_exam_list()
    
    if not exams:
        ttk.Label(self.content_frame, text="没有可用的考试").pack(pady=20)
        return
    
    # 创建Treeview显示考试列表
    tree_frame = ttk.Frame(self.content_frame)
    tree_frame.pack(fill=BOTH, expand=True, padx=20, pady=10)
    
    columns = ("id", "title", "duration", "created_at")
    self.exam_tree = ttk.Treeview(tree_frame, columns=columns, show="headings", selectmode="browse")
    
    # 设置列标题
    self.exam_tree.heading("id", text="ID")
    self.exam_tree.heading("title", text="考试标题")
    self.exam_tree.heading("duration", text="时长(分钟)")
    self.exam_tree.heading("created_at", text="创建时间")
    
    # 设置列宽
    self.exam_tree.column("id", width=50, anchor=CENTER)
    self.exam_tree.column("title", width=200)
    self.exam_tree.column("duration", width=80, anchor=CENTER)
    self.exam_tree.column("created_at", width=150, anchor=CENTER)
    
    # 添加滚动条
    scrollbar = ttk.Scrollbar(tree_frame, orient=VERTICAL, command=self.exam_tree.yview)
    self.exam_tree.configure(yscrollcommand=scrollbar.set)
    
    self.exam_tree.pack(side=LEFT, fill=BOTH, expand=True)
    scrollbar.pack(side=RIGHT, fill=Y)
    
    # 添加考试数据
    for exam in exams:
        self.exam_tree.insert("", END, values=(
            exam["id"],
            exam["title"],
            exam["duration"],
            exam["created_at"]
        ))
    
    # 按钮框架
    button_frame = ttk.Frame(self.content_frame)
    button_frame.pack(pady=10)
    
    ttk.Button(button_frame, text="查看问题", command=self.view_exam_questions).pack(side=LEFT, padx=5)
    ttk.Button(button_frame, text="删除考试", command=self.delete_exam).pack(side=LEFT, padx=5)
def view_exam_questions(self):
    """查看考试的问题"""
    selected = self.exam_tree.selection()
    if not selected:
        messagebox.showwarning("警告", "请先选择一个考试!")
        return
    
    item = self.exam_tree.item(selected[0])
    exam_id, exam_title = item["values"][0], item["values"][1]
    
    self.clear_content()
    self.status_var.set(f"查看问题 - {exam_title}")
    
    # 返回按钮
    ttk.Button(self.content_frame, text="返回", command=self.show_manage_exams).pack(anchor=NW, pady=5)
    
    # 标题
    ttk.Label(self.content_frame, text=f"考试 '{exam_title}' 的问题", 
             style="Header.TLabel").pack(pady=10)
    
    # 获取问题列表
    questions = self.system.get_exam_questions(exam_id)
    
    if not questions:
        ttk.Label(self.content_frame, text="这个考试还没有问题").pack(pady=20)
        return
    
    # 创建问题显示框架
    canvas = Canvas(self.content_frame)
    scrollbar = ttk.Scrollbar(self.content_frame, orient=VERTICAL, command=canvas.yview)
    scrollable_frame = ttk.Frame(canvas)
    
    scrollable_frame.bind(
        "<Configure>",
        lambda e: canvas.configure(
            scrollregion=canvas.bbox("all")
        )
    )
    
    canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
    canvas.configure(yscrollcommand=scrollbar.set)
    
    canvas.pack(side=LEFT, fill=BOTH, expand=True)
    scrollbar.pack(side=RIGHT, fill=Y)
    
    # 显示每个问题
    for idx, question in enumerate(questions, 1):
        q_frame = ttk.LabelFrame(scrollable_frame, text=f"问题 {idx}", padding=10)
        q_frame.pack(fill=X, padx=5, pady=5)
        
        # 问题内容
        ttk.Label(q_frame, text=question["question_text"], 
                 style="Question.TLabel").pack(anchor=W)
        
        # 问题类型
        ttk.Label(q_frame, text=f"类型: {question['question_type']}").pack(anchor=W)
        
        # 显示选项
        if question["options"]:
            options = json.loads(question["options"])
            ttk.Label(q_frame, text="选项:").pack(anchor=W)
            
            for opt in options:
                ttk.Label(q_frame, text=f"- {opt}").pack(anchor=W, padx=20)
        
        # 正确答案
        ttk.Label(q_frame, text=f"正确答案: {question['correct_answer']}").pack(anchor=W)
def delete_exam(self):
    """删除考试"""
    selected = self.exam_tree.selection()
    if not selected:
        messagebox.showwarning("警告", "请先选择一个考试!")
        return
    
    item = self.exam_tree.item(selected[0])
    exam_id, exam_title = item["values"][0], item["values"][1]
    
    confirm = messagebox.askyesno("确认", f"确定要删除考试 '{exam_title}' 吗?")
    if confirm:
        self.db.delete_exam(exam_id)
        messagebox.showinfo("成功", f"考试 '{exam_title}' 已删除!")
        self.show_manage_exams()
def show_take_exam(self):
    """显示参加考试界面"""
    self.clear_content()
    self.status_var.set("参加考试")
    
    # 返回按钮
    ttk.Button(self.content_frame, text="返回", command=self.show_main_menu).pack(anchor=NW, pady=5)
    
    # 标题
    ttk.Label(self.content_frame, text="选择考试", style="Header.TLabel").pack(pady=10)
    
    # 学生姓名输入
    name_frame = ttk.Frame(self.content_frame)
    name_frame.pack(pady=10)
    
    ttk.Label(name_frame, text="您的姓名:").pack(side=LEFT, padx=5)
    self.student_name_entry = ttk.Entry(name_frame, width=20)
    self.student_name_entry.pack(side=LEFT, padx=5)
    
    # 考试列表
    exams = self.system.get_exam_list()
    
    if not exams:
        ttk.Label(self.content_frame, text="没有可用的考试").pack(pady=20)
        return
    
    # 创建Treeview显示考试列表
    tree_frame = ttk.Frame(self.content_frame)
    tree_frame.pack(fill=BOTH, expand=True, padx=20, pady=10)
    
    columns = ("id", "title", "duration")
    self.exam_tree = ttk.Treeview(tree_frame, columns=columns, show="headings", selectmode="browse")
    
    # 设置列标题
    self.exam_tree.heading("id", text="ID")
    self.exam_tree.heading("title", text="考试标题")
    self.exam_tree.heading("duration", text="时长(分钟)")
    
    # 设置列宽
    self.exam_tree.column("id", width=50, anchor=CENTER)
    self.exam_tree.column("title", width=300)
    self.exam_tree.column("duration", width=100, anchor=CENTER)
    
    # 添加滚动条
    scrollbar = ttk.Scrollbar(tree_frame, orient=VERTICAL, command=self.exam_tree.yview)
    self.exam_tree.configure(yscrollcommand=scrollbar.set)
    
    self.exam_tree.pack(side=LEFT, fill=BOTH, expand=True)
    scrollbar.pack(side=RIGHT, fill=Y)
    
    # 添加考试数据
    for exam in exams:
        self.exam_tree.insert("", END, values=(
            exam["id"],
            exam["title"],
            exam["duration"]
        ))
    
    # 开始考试按钮
    ttk.Button(self.content_frame, text="开始考试", command=self.start_selected_exam).pack(pady=10)
def start_selected_exam(self):
    """开始选择的考试"""
    selected = self.exam_tree.selection()
    if not selected:
        messagebox.showwarning("警告", "请先选择一个考试!")
        return
    
    student_name = self.student_name_entry.get().strip()
    if not student_name:
        messagebox.showwarning("警告", "请输入您的姓名!")
        return
    
    item = self.exam_tree.item(selected[0])
    exam_id, exam_title, duration = item["values"][0], item["values"][1], item["values"][2]
    
    # 开始考试
    record_id = self.system.start_exam(exam_id, student_name)
    if record_id:
        self.show_exam_questions(exam_id, exam_title, duration, record_id)
    else:
        messagebox.showerror("错误", "无法开始考试!")
def show_exam_questions(self, exam_id, exam_title, duration, record_id):
    """显示考试问题界面"""
    self.clear_content()
    self.status_var.set(f"正在考试: {exam_title}")
    
    # 考试信息
    info_frame = ttk.Frame(self.content_frame)
    info_frame.pack(fill=X, padx=10, pady=10)
    
    ttk.Label(info_frame, text=f"考试: {exam_title}", style="Header.TLabel").pack(side=LEFT)
    self.time_label = ttk.Label(info_frame, text=f"剩余时间: {duration}:00")
    self.time_label.pack(side=RIGHT, padx=10)
    
    # 获取问题
    questions = self.system.get_exam_questions(exam_id)
    if not questions:
        messagebox.showerror("错误", "这个考试没有问题!")
        self.show_main_menu()
        return
    
    # 创建问题导航
    self.current_question_index = 0
    self.questions = questions
    self.record_id = record_id
    self.exam_duration = int(duration) * 60  # 转换为秒
    self.start_time = time.time()
    self.end_time = self.start_time + self.exam_duration
    
    # 创建问题显示区域
    self.question_display_frame = ttk.Frame(self.content_frame)
    self.question_display_frame.pack(fill=BOTH, expand=True, padx=20, pady=10)
    
    # 创建导航按钮
    nav_frame = ttk.Frame(self.content_frame)
    nav_frame.pack(fill=X, pady=10)
    
    ttk.Button(nav_frame, text="上一题", command=self.prev_question).pack(side=LEFT, padx=10)
    ttk.Button(nav_frame, text="下一题", command=self.next_question).pack(side=LEFT, padx=10)
    ttk.Button(nav_frame, text="提交考试", command=self.submit_exam).pack(side=RIGHT, padx=10)
    
    # 显示第一个问题
    self.display_current_question()
    
    # 启动计时器
    self.update_timer()
def display_current_question(self):
    """显示当前问题"""
    # 清除之前的显示
    for widget in self.question_display_frame.winfo_children():
        widget.destroy()
    
    question = self.questions[self.current_question_index]
    
    # 问题编号
    ttk.Label(self.question_display_frame, 
             text=f"问题 {self.current_question_index + 1}/{len(self.questions)}",
             style="Header.TLabel").pack(anchor=W, pady=5)
    
    # 问题内容
    ttk.Label(self.question_display_frame, 
             text=question["question_text"], 
             style="Question.TLabel").pack(anchor=W, pady=10)
    
    # 根据问题类型显示不同输入方式
    if question["question_type"] in ["single", "multiple"]:
        self.display_options_question(question)
    else:
        self.display_essay_question(question)
def display_options_question(self, question):
    """显示选择题"""
    options = json.loads(question["options"])
    self.answer_var = StringVar()
    
    for idx, option in enumerate(options):
        rb = ttk.Radiobutton(
            self.question_display_frame,
            text=option,
            variable=self.answer_var,
            value=chr(65 + idx),  # A, B, C, ...
            style="Option.TRadiobutton"
        )
        rb.pack(anchor=W, pady=2)
def display_essay_question(self, question):
    """显示问答题"""
    self.essay_answer = Text(self.question_display_frame, width=60, height=10, wrap=WORD)
    self.essay_answer.pack(fill=BOTH, expand=True, pady=10)
def prev_question(self):
    """显示上一题"""
    if self.current_question_index > 0:
        self.save_current_answer()
        self.current_question_index -= 1
        self.display_current_question()
def next_question(self):
    """显示下一题"""
    if self.current_question_index < len(self.questions) - 1:
        self.save_current_answer()
        self.current_question_index += 1
        self.display_current_question()
def save_current_answer(self):
    """保存当前问题的答案"""
    question = self.questions[self.current_question_index]
    q_type = question["question_type"]
    answer = None
    is_correct = 0  # 0-错误,1-正确(客观题自动判断)
    
    if q_type in ["single", "multiple"]:
        # 选择题答案
        selected = self.answer_var.get()
        answer = selected or ""
        if answer:
            # 自动判断是否正确(仅单选题)
            if q_type == "single" and answer == question["correct_answer"]:
                is_correct = 1
    else:
        # 问答题答案
        answer = self.essay_answer.get("1.0", END).strip()
        # 主观题需人工判分,此处暂不判断正确性
    
    if answer or q_type == "essay":  # 允许空答案(如未作答)
        self.system.submit_answer(
            question_id=question["id"],
            answer=answer,
            is_correct=is_correct
        )
def update_timer(self):
    """更新剩余时间显示"""
    elapsed = time.time() - self.start_time
    remaining = max(0, self.exam_duration - int(elapsed))
    
    if remaining <= 0:
        self.submit_exam()
        return
    
    # 当剩余时间少于5分钟时显示警告颜色
    if remaining <= 300:  # 300秒 = 5分钟
        self.time_label.configure(foreground="red")
    else:
        self.time_label.configure(foreground="black")
        
    minutes, seconds = divmod(remaining, 60)
    self.time_label.config(text=f"剩余时间: {int(minutes):02d}:{int(seconds):02d}")
    self.root.after(1000, self.update_timer)  # 每秒更新
def submit_exam(self):
    """提交考试"""
    # 保存最后一题答案
    self.save_current_answer()
    
    # 计算得分
    total = len(self.questions)
    cursor = self.db.conn.cursor()
    cursor.execute(
        "SELECT is_correct FROM student_answers WHERE exam_record_id=?",
        (self.record_id,)
    )
    correct = sum(row[0] for row in cursor.fetchall())
    score = int((correct / total) * 100) if total else 0
    
    # 完成考试
    self.system.finish_exam(score, total)
    
    # 显示结果
    self.show_exam_result(self.record_id)
def show_exam_result(self, record_id):
    """显示考试结果"""
    self.clear_content()
    record, questions = self.system.get_exam_result(record_id)
    
    if not record:
        messagebox.showerror("错误", "未找到考试记录!")
        self.show_main_menu()
        return
    
    # 结果标题
    ttk.Label(self.content_frame, text="考试结果", style="Header.TLabel").pack(pady=20)
    
    # 基本信息
    info_frame = ttk.Frame(self.content_frame)
    info_frame.pack(pady=10)
    
    ttk.Label(info_frame, text=f"考试名称: {record['title']}").pack(side=LEFT, padx=10)
    ttk.Label(info_frame, text=f"考生姓名: {record['student_name']}").pack(side=LEFT, padx=10)
    ttk.Label(info_frame, text=f"得分: {record['score']} / {record['total_questions']}").pack(side=LEFT, padx=10)
    
    # 问题详情
    for idx, q in enumerate(questions, 1):
        frame = ttk.LabelFrame(self.content_frame, text=f"问题 {idx}", padding=10)
        frame.pack(fill=X, padx=5, pady=5)
        
        # 问题内容
        ttk.Label(frame, text=q["question_text"], style="Question.TLabel").pack(anchor=W)
        
        # 考生答案
        if q["question_type"] in ["single", "multiple"]:
            answer = q["answer"] or "未作答"
            ttk.Label(frame, text=f"你的答案: {answer}").pack(anchor=W)
        else:
            answer = q["answer"] or "未作答"
            ttk.Label(frame, text=f"你的答案:\n{answer}", wraplength=500).pack(anchor=W)
        
        # 正确性判断
        correct = "正确" if q["is_correct"] else "错误"
        ttk.Label(frame, text=f"正确性: {correct}", 
                 foreground="green" if q["is_correct"] else "red").pack(anchor=W)
        
        # 正确答案
        if q["question_type"] != "essay":
            ttk.Label(frame, text=f"正确答案: {q['correct_answer']}").pack(anchor=W)
    
    # 返回按钮
    ttk.Button(self.content_frame, text="返回主菜单", command=self.show_main_menu).pack(pady=20)
def show_exam_results(self):
    """显示所有考试结果"""
    self.clear_content()
    self.status_var.set("查看考试结果")
    
    # 返回按钮
    ttk.Button(self.content_frame, text="返回", command=self.show_main_menu).pack(anchor=NW, pady=5)
    
    # 标题
    ttk.Label(self.content_frame, text="考试结果列表", style="Header.TLabel").pack(pady=10)
    
    # 获取结果数据
    results = self.system.get_all_results()
    
    if not results:
        ttk.Label(self.content_frame, text="没有已完成的考试结果").pack(pady=20)
        return
    
    # 创建Treeview显示结果
    tree_frame = ttk.Frame(self.content_frame)
    tree_frame.pack(fill=BOTH, expand=True, padx=20, pady=10)
    
    columns = ("id", "考试名称", "考生姓名", "得分", "总分", "开始时间", "结束时间")
    self.result_tree = ttk.Treeview(tree_frame, columns=columns, show="headings", selectmode="browse")
    
    # 设置列标题
    self.result_tree.heading("id", text="ID")
    self.result_tree.heading("考试名称", text="考试名称")
    self.result_tree.heading("考生姓名", text="考生姓名")
    self.result_tree.heading("得分", text="得分")
    self.result_tree.heading("总分", text="总分")
    self.result_tree.heading("开始时间", text="开始时间")
    self.result_tree.heading("结束时间", text="结束时间")
    
    # 设置列宽
    self.result_tree.column("id", width=50, anchor=CENTER)
    self.result_tree.column("考试名称", width=200)
    self.result_tree.column("考生姓名", width=150)
    self.result_tree.column("得分", width=80, anchor=CENTER)
    self.result_tree.column("总分", width=80, anchor=CENTER)
    self.result_tree.column("开始时间", width=150)
    self.result_tree.column("结束时间", width=150)
    
    # 添加滚动条
    scrollbar = ttk.Scrollbar(tree_frame, orient=VERTICAL, command=self.result_tree.yview)
    self.result_tree.configure(yscrollcommand=scrollbar.set)
    
    self.result_tree.pack(side=LEFT, fill=BOTH, expand=True)
    scrollbar.pack(side=RIGHT, fill=Y)
    
    # 添加数据
    for res in results:
        self.result_tree.insert("", END, values=(
            res["id"],
            res["title"],
            res["student_name"],
            res["score"],
            res["total_questions"],
            res["start_time"],
            res["end_time"]
        ))
    
    # 查看详情按钮
    ttk.Button(self.content_frame, text="查看详情", command=self.view_result_details).pack(pady=10)
def view_result_details(self):
    """查看考试结果详情"""
    selected = self.result_tree.selection()
    if not selected:
        messagebox.showwarning("警告", "请先选择一个考试结果!")
        return
    
    item = self.result_tree.item(selected[0])
    record_id = item["values"][0]
    self.show_exam_result(record_id)
def backup_data(self):
    """数据备份"""
    file_path = filedialog.asksaveasfilename(
        defaultextension=".db",
        filetypes=[("SQLite数据库", "*.db"), ("所有文件", "*.*")]
    )
    
    if not file_path:
        return
    
    try:
        # 复制数据库文件
        shutil.copyfile(self.db.db_file, file_path)
        messagebox.showinfo("成功", "数据备份完成!")
    except Exception as e:
        messagebox.showerror("错误", f"备份失败: {str(e)}")
if name == "main":
root = Tk()
app = ExamSystemGUI(root)
root.mainloop()
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号