结对编程作业:简易在线考试系统的设计与实现

第七周课程作业:简易在线考试系统的设计与实现

>课程: 软件开发与创新课程设计
>作者学号: 2452222
>结对队友学号: 2452223
>日期: 2026年4月18日


一、需求分析

根据课程要求,本次结对编程作业需要开发一个支持客观题考试的在线系统。经过讨论,我们将系统功能划分为三大核心模块:

模块 功能说明
题目管理模块 支持单选题、多选题、判断题的增删改查(CRUD),仅限管理员角色操作
考生答题模块 学生登录后进入考试,支持按题型(单选/多选/判断)分页展示、限时答题(180秒)、答案实时保存
自动判分模块 交卷或超时后自动计算得分,生成可视化成绩报告,包含错题解析与正确选项对比

非功能性需求有:

  • 采用了 C/S 架构,本地运行,数据以 JSON 文件持久化
  • 界面使用 PySide6 构建,保证跨平台兼容性
  • 代码结构清晰,职责分离(数据层、界面层分离)

二、系统架构设计

2.1 技术选型

  • 开发语言: Python3.x
  • GUI 框架: PySide6(Qt for Python)
  • 数据存储: JSON文件(users.jsonquestions.json
  • 架构模式: 经典三层架构(数据访问层->业务逻辑层->表示层)

2.2 文件结构

ExamSystem/
|—— main.py              #程序入口,初始化QApplication
|—— window.py            #所有界面窗口类(登录、考试、成绩、管理)
|—— data_manager.py      #数据访问对象(DAO),负责JSON读写
|—— data/
    |—— users.json           #用户数据(用户名、密码、角色)
    |—— questions.json       #题库数据(题目、选项、答案、解析)

2.3 核心类图


三、数据库(JSON)结构设计

3.1 用户表 users.json

[
    {
        "username": "admin",
        "password": "123",
        "role": "admin"
    },
    {
        "username": "student",
        "password": "123",
        "role": "student"
    }
]

3.2 题目表 questions.json

[
    {
        "id": 1,
        "type": "single",
        "question": "Python的作者是谁?",
        "options": [
            "A. Guido",
            "B. Linus",
            "C. Bill"
        ],
        "answer": [
            "A"
        ],
        "analysis": "Guido van Rossum 是 Python 之父"
    },
    {
        "id": 2,
        "type": "multiple",
        "question": "以下哪些是编程语言?",
        "options": [
            "A. Python",
            "B. Java",
            "C. HTML"
        ],
        "answer": [
            "A",
            "B"
        ],
        "analysis": "HTML不是编程语言"
    },
    {
        "id": 3,
        "type": "judge",
        "question": "2+3=6",
        "options": [
            "A. 正确",
            "B. 错误"
        ],
        "answer": [
            "B"
        ],
        "analysis": "基础数学"
    }
]

字段说明:

  • type:枚举值 single(单选)、multiple(多选)、judge(判断)
  • options:选项列表,判断题固定为 ["A. 正确", "B. 错误"]
  • answer:答案数组,多选支持多个答案(如 ["A", "B"])

四、详细设计与核心代码

4.1 数据访问层(data_manager.py)

点击查看代码
import json
import os

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

class DataManager:
    QUESTION_FILE = os.path.join(BASE_DIR, "data", "questions.json")
    USER_FILE = os.path.join(BASE_DIR, "data", "users.json")


    @staticmethod
    def load_questions():
        if not os.path.exists(DataManager.QUESTION_FILE):
            return []
        with open(DataManager.QUESTION_FILE, "r", encoding="utf-8") as f:
            return json.load(f)

    @staticmethod
    def save_questions(questions):
        with open(DataManager.QUESTION_FILE, "w", encoding="utf-8") as f:
            json.dump(questions, f, ensure_ascii=False, indent=4)

    @staticmethod
    def validate_user(username, password):
        users = []
        if os.path.exists(DataManager.USER_FILE):
            with open(DataManager.USER_FILE, "r", encoding="utf-8") as f:
                users = json.load(f)
        for u in users:
            if u["username"] == username and u["password"] == password:
                return u
        return None

设计亮点:

  • 使用 BASE_DIR 保证相对路径的健壮性,避免在不同工作目录下运行时找不到文件。
  • validate_user 返回完整用户字典,便于上层根据 role 字段进行路由。

4.2 程序入口(main.py)

点击查看代码
import sys
from PySide6.QtWidgets import QApplication
from window import LoginWindow

if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = LoginWindow()
    window.resize(300, 200)
    window.show()

    sys.exit(app.exec())

4.3 表示层(window.py)

由于 window.py 代码过长,这里只展示各个核心部分代码,完整代码贴在了此文章最后第九部分~

4.3.1 登录窗口(LoginWindow)

点击查看代码
class LoginWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("登录")
        # 布局与样式初始化...
        
    def handle_login(self):
        username = self.input_user.text()
        password = self.input_pwd.text()
        user = DataManager.validate_user(username, password)
        
        if not user:
            QMessageBox.warning(self, "错误", "用户名或密码错误")
            return
        
        if user["role"] == "student":
            self.enter_exam()
        elif user["role"] == "admin":
            self.enter_admin()

4.3.2 考试窗口(ExamWindow)

(1)题目分组与分页渲染

点击查看代码
def group_questions(self, questions):
    groups = {"single": [], "multiple": [], "judge": []}
    for q in questions:
        groups[q["type"]].append(q)
    return groups

(2)动态控件生成

点击查看代码
def render_section(self):
    self.clear_layout()
    section = self.sections[self.current_section_index]
    # ...
    for q in questions:
        q_box = QGroupBox(q["question"])
        vbox = QVBoxLayout()
        widgets = []
        for opt in q["options"]:
            if section in ["single", "judge"]:
                btn = QRadioButton(opt)
            else:
                btn = QCheckBox(opt)
            vbox.addWidget(btn)
            widgets.append(btn)
        # ...

(3)答案实时缓存

点击查看代码
def save_answers(self):
    for qid, widgets in self.current_widgets.items():
        selected = [w.text()[0] for w in widgets if w.isChecked()]
        self.answers[qid] = selected

(4)倒计时与自动交卷

点击查看代码
def update_time(self):
    self.time_left -= 1
    self.timer_label.setText(f"剩余时间:{self.time_left}s")
    if self.time_left <= 0:
        self.submit_exam()

4.3.3 成绩报告窗口(ResultWindow)

点击查看代码
def submit_exam(self):
    self.save_answers()
    self.timer.stop()
    score = 0
    wrong = []
    for q in self.questions:
        qid = q["id"]
        correct = set(q["answer"])
        user = set(self.answers.get(qid, []))
        if correct == user:
            score += 1
        else:
            wrong.append({"question": q, "user_answer": list(user)})
    self.result = ResultWindow(score, len(self.questions), wrong)
    self.result.show()
    self.close()

判分逻辑说明:

  • 使用 set 比较,无视答案顺序,保证多选题判分的严谨性。
  • 错题解析区展示:题目原文、用户答案(红色)、正确答案(绿色)、文字解析。

4.3.4 管理后台(AdminWindow)

点击查看代码
def change_type(self, current_text):
    if current_text == "判断题":
        self.options_edit.setText("A. 正确\nB. 错误")
    self.options_edit.setReadOnly(current_text == "判断题")

def reorder_question_ids(self):
    for idx, q in enumerate(self.questions, start=1):
        q["id"] = idx

五、运行结果与截图

5.1 登录界面

说明:输入用户名/密码后的登录界面,可分别登录学生账号和管理员账号。


5.2 学生考试界面

(1)单选题部分

说明:考试进行中的界面,可见倒计时、题型标题、单选按钮组。


(2)多选题部分

说明:多选题的复选框状态,证明分页功能正常。


(3)判断题部分

说明:判断题也是单选按钮组。


5.3 成绩报告界面

(1)成绩报告截图(包含错题解析)

说明:展示交卷后的结果页面,包含得分(如1/3)、错题卡片(红字用户答案、绿字正确答案、解析文本)。第二张图片是放大后的页面展示。


(2)满分通过截图

说明:可额外展示全部答对时的界面,显示"恭喜!你全对了!"。


5.4 管理员题目管理界面

(1)管理后台截图(包含题目列表与编辑区)

说明:左侧是题目列表,右侧是表单(题型下拉框、题目文本、选项、答案、解析),表单内容可进行编辑更改。


(2)新增题目操作截图

说明:点击"新增"后跳出空表单,填写信息并点击保存,更新列表和表单。


(3)删除题目操作截图

说明:点击题目删除跳出确认,确认删除后更新列表


5.5 数据文件变化

questions.json内容变化

说明:新增题目之后,用VS Code或记事本打开JSON文件,可见保存的数据结构。


六、算法设计思路总结

  • 数据驱动视图: 所有界面均基于questions列表动态生成,而非硬编码控件。新增题型时只需扩展数据结构和渲染逻辑即可。

  • 状态管理: ExamWindow 中通过self.answers字典维护答题状态,键为题目ID,值为选项字母列表,实现答案的跨页面持久化。

  • 集合判分: 利用 Pythonset的无序性和哈希比较,将时间复杂度降至 O(n),且代码简洁。

  • 防御式编程: 文件不存在时返回空列表而非抛出异常;登录验证失败给予明确提示;删除操作前弹出确认对话框。


七、结对编程作业体会

本次实验采用结对编程模式完成,我们两人先共同讨论系统架构与界面原型,随后轮流担任编程手和审核员的角色。

7.1 角色分工与协作

  • 第一阶段(架构设计): 共同分析需求,然后确定使用 PySide6 作为 GUI 框架,JSON 作为轻量级存储。画了简单的界面草图,明确了三大窗口的职责边界。

  • 第二阶段(编码实现): 交替进行编码。一人负责敲代码时,另一人负责审查语法、思考补充。

  • 第三阶段(测试与调优): 共同进行黑盒测试,分别用学生账号和管理员账号走完全部流程,发现了“判断题选项未锁定”、“多选题答案顺序导致误判”等问题并进行了修复。


7.2 结对编程的收获

  • 代码质量显著提升: 实时 Code Review 使得低级错误在诞生之初就被消灭,减少了后期 Debug 的时间。

  • 知识互补: 我们一人对 PySide6 信号槽机制更熟悉,另一人对 Python 文件操作和 JSON 处理更有经验,通过讲解加深了双方的理解。

  • 设计思路更严谨: 在审核员的监督下,在写每一段代码前都要先说明"为什么要这样写",促使采用了更合理的DataManager静态类设计和集合判分算法。

  • 沟通与表达能力: 技术方案需要用语言向对方表达,锻炼了我们把抽象的思路具象化的能力,为这也是团队协作所不可或缺的重要部分。


7.3 存在的不足与改进方向

  • 界面美观度: 目前仅使用基础样式表(QSS),后续可引入 Qt Designer 设计更现代的界面。

  • 数据持久化: JSON 文件在并发写入时存在风险,后续可迁移至 SQLite 轻量级数据库。

  • 功能扩展: 当前缺少“注册功能”和“考试历史记录”,后续可以新增这些功能。


八、总结

  在这次的结对编程作业中,我们完成了一个功能完整的简易在线考试系统,更重要的是体验了敏捷开发中“两人一组、实时协作”的工作模式。从需求分析到架构设计,从编程到测试,这每一步都体现了团队合作的意义所在。
  不得不承认结对编程让代码更健壮、思路更清晰,也让编程过程不再孤单,说到这我想到现在 OPC 的发展趋势让许多自许的“超级个体”容易走向极端化,他们在工作过程可能确实不再需要编程同伴是人类了,成天只是与AI打交道。但是我想那样结对编程的优点可能也会更加凸显吧,毕竟能说上话的真实存在的同伴,沟通起来比冰冷的机器更具有一份人情味~😄


九、window.py 完整代码

点击查看代码
from PySide6.QtWidgets import *
from PySide6.QtCore import Qt, QTimer
from data_manager import DataManager


class LoginWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("登录")

        layout = QVBoxLayout()
        layout.setSpacing(15)
        layout.setContentsMargins(40, 40, 40, 40)

        title = QLabel("在线考试系统")
        title.setStyleSheet("font-size: 20px; font-weight: bold;")
        title.setAlignment(Qt.AlignCenter)

        self.input_user = QLineEdit()
        self.input_user.setPlaceholderText("用户名")
        self.input_user.setFixedHeight(30)

        self.input_pwd = QLineEdit()
        self.input_pwd.setPlaceholderText("密码")
        self.input_pwd.setEchoMode(QLineEdit.Password)
        self.input_pwd.setFixedHeight(30)

        self.btn_login = QPushButton("登录")
        self.btn_login.setFixedHeight(35)

        layout.addWidget(title)
        layout.addWidget(self.input_user)
        layout.addWidget(self.input_pwd)
        layout.addWidget(self.btn_login)

        self.setLayout(layout)

        self.btn_login.clicked.connect(self.handle_login)

    def handle_login(self):
        username = self.input_user.text()
        password = self.input_pwd.text()

        user = DataManager.validate_user(username, password)

        if not user:
            QMessageBox.warning(self, "错误", "用户名或密码错误")
            return

        if user["role"] == "student":
            self.enter_exam()

        elif user["role"] == "admin":
            self.enter_admin()

    def enter_exam(self):
        questions = DataManager.load_questions()

        if not questions:
            QMessageBox.warning(self, "错误", "题库为空")
            return

        self.exam = ExamWindow(questions)
        self.exam.resize(280, 400)
        self.exam.show()
        self.close()

    def enter_admin(self):
        self.admin = AdminWindow()
        self.admin.show()
        self.close()


class ExamWindow(QWidget):
    def __init__(self, questions):
        super().__init__()
        self.setWindowTitle("考试")

        self.questions = questions
        self.answers = {}

        self.groups = self.group_questions(questions)
        self.sections = ["single", "multiple", "judge"]
        self.current_section_index = 0

        self.time_left = 180

        self.layout = QVBoxLayout()

        self.timer_label = QLabel()
        self.timer_label.setStyleSheet("color:red; font-size:16px;")
        self.layout.addWidget(self.timer_label)

        self.section_label = QLabel()
        self.section_label.setStyleSheet("font-size:16px;")
        self.layout.addWidget(self.section_label)

        self.scroll = QScrollArea()
        self.scroll_widget = QWidget()
        self.scroll_layout = QVBoxLayout()

        self.scroll_widget.setLayout(self.scroll_layout)
        self.scroll.setWidget(self.scroll_widget)
        self.scroll.setWidgetResizable(True)

        self.layout.addWidget(self.scroll)

        btn_layout = QVBoxLayout()
        self.btn_next = QPushButton("下一部分")
        self.btn_back = QPushButton("上一部分")
        self.btn_submit = QPushButton("提交")
        btn_layout.addWidget(self.btn_back)
        btn_layout.addWidget(self.btn_next)
        btn_layout.addWidget(self.btn_submit)
        self.layout.addLayout(btn_layout)

        self.setLayout(self.layout)

        self.btn_back.clicked.connect(self.back_section)
        self.btn_next.clicked.connect(self.next_section)
        self.btn_submit.clicked.connect(self.submit_exam)

        self.timer = QTimer()
        self.timer.timeout.connect(self.update_time)
        self.timer.start(1000)

        self.render_section()

    def group_questions(self, questions):
        groups = {"single": [], "multiple": [], "judge": []}
        for q in questions:
            groups[q["type"]].append(q)
        return groups

    def update_time(self):
        self.time_left -= 1
        self.timer_label.setText(f"剩余时间:{self.time_left}s")

        if self.time_left <= 0:
            self.submit_exam()

    def clear_layout(self):
        while self.scroll_layout.count():
            item = self.scroll_layout.takeAt(0)
            if item.widget():
                item.widget().deleteLater()

    def render_section(self):
        self.clear_layout()

        section = self.sections[self.current_section_index]
        section_map = {
            "single": "单选题",
            "multiple": "多选题",
            "judge": "判断题"
        }

        self.section_label.setText(f"当前部分:{section_map[section]}")

        self.current_widgets = {}

        questions = self.groups[section]

        for q in questions:
            q_box = QGroupBox(q["question"])
            q_box = QGroupBox(q["question"])
            vbox = QVBoxLayout()

            widgets = []

            for opt in q["options"]:
                if section in ["single", "judge"]:
                    btn = QRadioButton(opt)
                else:
                    btn = QCheckBox(opt)

                vbox.addWidget(btn)
                widgets.append(btn)

            q_box.setLayout(vbox)
            self.scroll_layout.addWidget(q_box)

            self.current_widgets[q["id"]] = widgets

            if q["id"] in self.answers:
                for w in widgets:
                    if w.text()[0] in self.answers[q["id"]]:
                        w.setChecked(True)

        self.btn_next.setEnabled(self.current_section_index < len(self.sections) - 1)
        self.btn_back.setEnabled(self.current_section_index > 0)

    def save_answers(self):
        for qid, widgets in self.current_widgets.items():
            selected = [w.text()[0] for w in widgets if w.isChecked()]
            self.answers[qid] = selected

    def next_section(self):
        self.save_answers()

        if self.current_section_index < len(self.sections) - 1:
            self.current_section_index += 1
            self.render_section()
    
    def back_section(self):
        self.save_answers()

        if self.current_section_index > 0:
            self.current_section_index -= 1
            self.render_section()

    def submit_exam(self):
        self.save_answers()
        self.timer.stop()

        score = 0
        wrong = []

        for q in self.questions:
            qid = q["id"]
            correct = set(q["answer"])
            user = set(self.answers.get(qid, []))

            if correct == user:
                score += 1
            else:
                wrong.append({
                    "question": q,
                    "user_answer": list(user)
                })

        self.result = ResultWindow(score, len(self.questions), wrong)
        self.result.show()
        self.close()


class ResultWindow(QWidget):
    def __init__(self, score, total, wrong):
        super().__init__()
        self.setWindowTitle("成绩报告")
        self.resize(400, 500)

        main_layout = QVBoxLayout()
        main_layout.setSpacing(15)
        main_layout.setContentsMargins(20, 20, 20, 20)

        title = QLabel("考试结果")
        title.setAlignment(Qt.AlignCenter)
        title.setStyleSheet("font-size:22px; font-weight:bold;")
        main_layout.addWidget(title)

        score_label = QLabel(f"{score} / {total}")
        score_label.setAlignment(Qt.AlignCenter)

        percent = score / total if total else 0
        if percent >= 0.8:
            color = "#4CAF50"
        elif percent >= 0.6:
            color = "#ff9800"
        else:
            color = "#f44336"

        score_label.setStyleSheet(f"""
            font-size:26px;
            font-weight:bold;
            color:{color};
        """)

        main_layout.addWidget(score_label)

        line = QFrame()
        line.setFrameShape(QFrame.HLine)
        line.setStyleSheet("color:#ccc;")
        main_layout.addWidget(line)

        wrong_title = QLabel("错题解析")
        wrong_title.setStyleSheet("font-size:16px; font-weight:bold;")
        main_layout.addWidget(wrong_title)

        scroll = QScrollArea()
        scroll.setWidgetResizable(True)

        container = QWidget()
        container_layout = QVBoxLayout()
        container_layout.setSpacing(10)

        if not wrong:
            empty_label = QLabel("恭喜!你全对了!")
            empty_label.setAlignment(Qt.AlignCenter)
            empty_label.setStyleSheet("color:green; font-size:16px;")
            container_layout.addWidget(empty_label)
        else:
            for w in wrong:
                q = w["question"]

                card = QFrame()
                card.setStyleSheet("""
                    QFrame {
                        border: 1px solid #ddd;
                        border-radius: 8px;
                        padding: 10px;
                    }
                """)

                vbox = QVBoxLayout()

                q_label = QLabel(f"题目:{q['question']}")
                q_label.setWordWrap(True)

                user_label = QLabel(f"你的答案:{','.join(w['user_answer']) if w['user_answer'] else '<未选择>'}")
                user_label.setStyleSheet("color:#f44336;")

                correct_label = QLabel(f"正确答案:{','.join(q['answer'])}")
                correct_label.setStyleSheet("color:#4CAF50;")

                analysis_label = QLabel(f"解析:{q['analysis']}")
                analysis_label.setWordWrap(True)

                vbox.addWidget(q_label)
                vbox.addWidget(user_label)
                vbox.addWidget(correct_label)
                vbox.addWidget(analysis_label)

                card.setLayout(vbox)
                container_layout.addWidget(card)

        container.setLayout(container_layout)
        scroll.setWidget(container)

        main_layout.addWidget(scroll)

        btn_close = QPushButton("关闭")
        btn_close.setFixedHeight(35)
        btn_close.clicked.connect(self.close)

        main_layout.addWidget(btn_close)

        self.setLayout(main_layout)


class AdminWindow(QWidget):
    TYPE_MAP_TO_CH = {
    "single": "单选题",
    "multiple": "多选题",
    "judge": "判断题"
    }
    CH_MAP_TO_TYPE = {
        "单选题": "single",
        "多选题": "multiple",
        "判断题": "judge",
    }

    def __init__(self):
        super().__init__()
        self.setWindowTitle("题目管理")

        self.questions = DataManager.load_questions()

        main_layout = QHBoxLayout()

        self.list_widget = QListWidget()
        self.list_widget.setMinimumWidth(250)
        main_layout.addWidget(self.list_widget, 2)

        form_layout = QVBoxLayout()
        form_layout.setSpacing(10)

        self.type_box = QComboBox()
        self.type_box.addItems(self.TYPE_MAP_TO_CH.values())

        self.question_edit = QLineEdit()
        self.options_edit = QTextEdit()
        self.answer_edit = QLineEdit()
        self.analysis_edit = QTextEdit()

        form_layout.addWidget(QLabel("题型"))
        form_layout.addWidget(self.type_box)

        form_layout.addWidget(QLabel("题目"))
        form_layout.addWidget(self.question_edit)

        form_layout.addWidget(QLabel("选项(每行一个,如 A.xxx)"))
        self.question_edit.setFixedHeight(30)
        form_layout.addWidget(self.options_edit)

        form_layout.addWidget(QLabel("答案(如 A 或 A,B)"))
        self.answer_edit.setFixedHeight(30)
        form_layout.addWidget(self.answer_edit)

        form_layout.addWidget(QLabel("解析"))
        form_layout.addWidget(self.analysis_edit)

        btn_layout = QHBoxLayout()

        self.btn_add = QPushButton("新增")
        self.btn_add.setFixedHeight(35)
        btn_layout.addWidget(self.btn_add)
        self.btn_save = QPushButton("保存")
        self.btn_save.setFixedHeight(35)
        btn_layout.addWidget(self.btn_save)
        self.btn_delete = QPushButton("删除")
        self.btn_delete.setFixedHeight(35)
        btn_layout.addWidget(self.btn_delete)

        form_layout.addLayout(btn_layout)
        main_layout.addLayout(form_layout, 3)


        title = QLabel("题目管理系统")
        title.setStyleSheet("font-size:18px; font-weight:bold;")
        form_layout.insertWidget(0, title)

        self.setLayout(main_layout)

        self.list_widget.currentRowChanged.connect(self.load_selected)
        self.type_box.currentTextChanged.connect(self.change_type)
        self.btn_add.clicked.connect(self.add_question)
        self.btn_save.clicked.connect(self.save_question)
        self.btn_delete.clicked.connect(self.delete_question)

        self.refresh_list()
        self.list_widget.setCurrentRow(0)

    def refresh_list(self):
        self.list_widget.clear()
        for q in self.questions:
            self.list_widget.addItem(f"{q['id']}. {q['question']}")

    def reorder_question_ids(self):
        for idx, q in enumerate(self.questions, start=1):
            q["id"] = idx

    def load_selected(self, index):
        if index < 0:
            return

        q = self.questions[index]

        self.type_box.setCurrentText(self.TYPE_MAP_TO_CH[q["type"]])
        self.question_edit.setText(q["question"])
        self.options_edit.setText("A. 正确\nB. 错误" if q["type"] == "judge" else "\n".join(q["options"]))
        self.options_edit.setReadOnly(q["type"] == "judge")
        self.answer_edit.setText(",".join(q["answer"]))
        self.analysis_edit.setText(q["analysis"])
    
    def change_type(self, current_text):
        if current_text == "判断题":
            self.options_edit.setText("A. 正确\nB. 错误")
        self.options_edit.setReadOnly(current_text == "judge")

    def add_question(self):
        new_q = {
            "id": len(self.questions) + 1,
            "type": "single",
            "question": "",
            "options": [],
            "answer": [],
            "analysis": ""
        }

        self.questions.append(new_q)
        self.refresh_list()
        self.list_widget.setCurrentRow(len(self.questions) - 1)

    def save_question(self):
        index = self.list_widget.currentRow()
        if index < 0:
            return

        q = self.questions[index]

        q["type"] = self.CH_MAP_TO_TYPE[self.type_box.currentText()]
        q["question"] = self.question_edit.text()
        q["options"] = self.options_edit.toPlainText().split("\n")
        q["answer"] = [a.strip() for a in self.answer_edit.text().split(",")]
        q["analysis"] = self.analysis_edit.toPlainText()

        self.reorder_question_ids()
        DataManager.save_questions(self.questions)
        self.refresh_list()

        QMessageBox.information(self, "成功", "保存成功")

    def delete_question(self):
        index = self.list_widget.currentRow()
        if index < 0:
            return

        reply = QMessageBox.question(
            self, "确认", "确定删除该题?"
        )

        if reply == QMessageBox.Yes:
            self.questions.pop(index)
            self.reorder_question_ids()
            DataManager.save_questions(self.questions)
            self.refresh_list()

posted @ 2026-04-18 18:27  chenng77  阅读(11)  评论(0)    收藏  举报