吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]
查看: 11101|回复: 91
上一主题 下一主题
收起左侧

[Python 原创] 自建题库刷题2.0(适合单选、多选、判断)

  [复制链接]
跳转到指定楼层
楼主
dzxsl 发表于 2025-4-10 17:38 回帖奖励
1、页面风格改变
【1.0版】


【2.0版】


2、功能改变
①新增单题模式、多题模式、背题模式
②单题模式下增加自定义设置是否自动跳转、是否显示答案、是否自动提交,自定义设置的参数会保存,不用每次都重新设置
③每个题目做题的记录会临时记录,不会因切换题目而消失
④增加题目字体大小调节
⑤题库选择方式改变,“题库”文件夹内可以新建子文件夹存放题库,方便题库管理
⑥多题模式下实现分页显示,每页最多50个
⑦导入题库就是之前的转换;点击前,在题库选择面(左侧页面)点击要转入的文件夹,新转换的题库就会加入的选中的文件夹内;未选择默认转入“题库”文件夹

3、源代码
1.0版 1.0.rar (7.8 KB, 下载次数: 33)

2.0版 2.0.rar (13.24 KB, 下载次数: 408)

4、编译好的
https://pan.baidu.com/s/1TZtBco5bmv0auZV5jPPcXg 提取码: 52pj

---------------------------------------------------------------------------------------------------------------------------
欢迎大家学习交流!(禁止商用)
[Python] 纯文本查看 复制代码
import os
import json
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from collections import defaultdict
import re
import time
import random

class 做题软件:
    '''----------------------------------------UI--------------------------------------------------'''
    def __init__(self, 主窗口):
        self.主窗口 = 主窗口
        self.主窗口.title("刷题助手")
        self.当前题库 = []
        self.错题本 = []
        self.题目卡片组 = []
        self.做题记录文件 = os.path.join("date","做题记录.json")
        self.错题本文件 = os.path.join("date","错题本.json")
        self.配置文件 = os.path.join("date","配置信息.json")
        self.题目状态 = defaultdict(dict)  # {题目ID: {状态: 对错, 正确次数: 0,错误次数:0}}
        self.错题记录 = []   # {题目ID: 错误次数}
        self.题号按钮组 = []
        self.当前题库名称 = ""
        self.错题模式 = False  # 是否处于错题练习模式
        self.字体大小 = 12  # 默认字体大小
        self.用户答案 = {}  # {题目ID: 答案}
        self.多题模式 = False  # 新增模式状态标识
        self.当前显示卡片索引 = 0  #跟踪当前显示的卡片索引
        self.当前页数 = 1
        self.总页数 = 1
        self.每页题数 = 50  # 可调整每页显示题目数
        self.自动跳转 = tk.BooleanVar(value=True)
        self.显示答案 = tk.BooleanVar(value=True)
        self.自动提交单选 = tk.BooleanVar(value=True)
        self.背题模式 = tk.BooleanVar(value=False)
        '''--------左侧面板------------------------'''
        # 创建左侧面板并保存为实例变量
        self.左侧面板 = tk.Frame(主窗口, bd=0, relief=tk.FLAT, padx=10, pady=10, width=250)
        self.左侧面板.pack_propagate(False)  # 禁止子控件改变面板尺寸
        self.左侧面板.pack(side=tk.LEFT, fill=tk.Y, anchor="nw")
        self.题库树 = ttk.Treeview(self.左侧面板, show="tree", selectmode="browse")
        ysb = ttk.Scrollbar(self.左侧面板, orient="vertical", command=self.题库树.yview)
        self.题库树.configure(yscroll=ysb.set)
        self.题库树.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)# 布局
        ysb.pack(side=tk.RIGHT, fill=tk.Y)       
        # 初始化题库树
        self.题库根路径 = "题库"
        self.题库树.insert("", "end", text="题库", iid=self.题库根路径, 
                          values=("dir", self.题库根路径), open=True)
        self.加载子题库(self.题库根路径, parent=self.题库根路径)        
        # 绑定事件
        self.题库树.bind("<<TreeviewOpen>>", self.展开目录)
        self.题库树.bind("<<TreeviewSelect>>", self.选择题目库)
        
        self.中间面板 = tk.Frame(主窗口, bd=0, relief=tk.FLAT, padx=10, pady=10, width=350)
        self.中间面板.pack_propagate(False)  # 禁止子控件改变面板尺寸
        self.中间面板.pack(side=tk.LEFT, fill=tk.Y, anchor="nw")
        # 配置区域
        配置面板 = tk.Frame(self.中间面板)
        配置面板.pack(pady=5, fill=tk.X)

        tk.Checkbutton(配置面板, 
                     text="自动跳转",
                     variable=self.自动跳转,
                     command=self.保存配置记录).pack(side=tk.LEFT)
        
        tk.Checkbutton(配置面板,
                     text="显示答案",
                     variable=self.显示答案,
                     command=self.保存配置记录).pack(side=tk.LEFT)
        
        tk.Checkbutton(配置面板,
                     text="单选自动提交",
                     variable=self.自动提交单选,
                     command=self.保存配置记录).pack(side=tk.LEFT)
        tk.Checkbutton(配置面板,
                     text="背题模式",
                     variable=self.背题模式,
                     command=self.切换背题模式).pack(side=tk.LEFT)
            
        # 操作按钮区域
        操作框 = tk.Frame(self.中间面板)
        操作框.pack(pady=10)
        self.作答区 = tk.Frame(self.中间面板)
        self.作答区.pack(pady=10)
         # 初始化时创建所有按钮(但暂时隐藏)
        self.上一题按钮 = tk.Button(self.作答区, text="上一题", command=self.上一题)
        self.下一题按钮 = tk.Button(self.作答区, text="下一题", command=self.下一题)
        self.提交按钮 = tk.Button(self.作答区, text="提交所有", command=self.提交所有答案)
        self.上一页按钮 = tk.Button(self.作答区, text="上一页", command=lambda: self.切换页数(-1),state=tk.NORMAL if self.当前页数 >1 else tk.DISABLED)
        self.页数标签 = tk.Label(self.作答区, text=f"{self.当前页数}/{self.总页数}")
        self.下一页按钮 = tk.Button(self.作答区, text="下一页", command=lambda: self.切换页数(1),state=tk.NORMAL if self.当前页数 < self.总页数 else tk.DISABLED)
        # 默认显示单题按钮
        self.显示模式按钮()
        self.模式切换按钮 = tk.Button(操作框, text="切换多题模式", command=self.切换显示模式)
        self.模式切换按钮.pack(side=tk.LEFT, padx=10)
        self.错题按钮 = tk.Button(操作框, text="错题练习", command=self.显示错题本)
        self.错题按钮.pack(side="left", padx=5)
        self.编辑按钮 = tk.Button(操作框, text="编辑题目", command=self.编辑题目)
        self.编辑按钮.pack(side="left", padx=5)
        # 导入按钮
        self.导入按钮 = tk.Button(操作框, text="导入题库", command=self.导入题目)
        self.导入按钮.pack(side=tk.LEFT, padx=5)    
        #调节字体大小
        字体调节行 = tk.Frame(self.中间面板)
        字体调节行.pack(anchor="nw", pady=5)
        tk.Label(字体调节行, text="字体大小调节:").pack(side=tk.LEFT)
        self.增大按钮 = tk.Button(字体调节行, text="+", command=lambda: self.调整字体大小(1), width=3)
        self.增大按钮.pack(anchor="nw", side=tk.LEFT, padx=2)
        self.减小按钮 = tk.Button(字体调节行, text="-", command=lambda: self.调整字体大小(-1), width=3)
        self.减小按钮.pack(anchor="nw", side=tk.LEFT, padx=2)

        # 加载历史数据
        self.加载做题记录()
        self.加载错题本()
        self.导航框 = None  # 导航框引用       
        '''--------右侧面板------------------------'''
        # 右侧容器(用于管理题目)
        self.右侧容器 = tk.Frame(主窗口)
        self.右侧容器.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
        # 初始化多题容器为None
        self.多题容器 = None
        self.滚动条 = None
        self.内层框架 = None
        self.右侧容器.bind("<Configure>", self.调整布局)
        self.主窗口.update_idletasks()
        self.加载配置信息()

    '''---------------------------------------------功能------------------------------------------------'''
    def 加载子题库(self, path, parent):
        """精准加载目录结构"""
        # 获取现有子节点
        现有文件 = [self.题库树.item(child, "text") for child in self.题库树.get_children(parent)]
        
        try:
            for entry in sorted(os.listdir(path)):
                # 跳过已存在的项
                if entry in 现有文件:
                    continue
                
        except Exception as e:
            print(f"加载错误:{str(e)}")
        try:
            # 先清空占位符(如果有)
            for child in self.题库树.get_children(parent):
                if self.题库树.item(child, "values")[0] == "placeholder":
                    self.题库树.delete(child)
            # 获取目录项并按文件夹优先排序
            entries = sorted(os.listdir(path), 
                           key=lambda x: (not os.path.isdir(os.path.join(path, x)), x))
            for entry in entries:
                entry_path = os.path.join(path, entry)
                # 跳过隐藏文件(以.开头)
                if entry.startswith('.'):
                    continue
                if os.path.isdir(entry_path):
                    # 创建目录节点并添加展开指示符
                    node = self.题库树.insert(parent, "end", 
                                            text=f"&#128193; {entry}",
                                            values=("dir", entry_path),
                                            tags=("folder",))
                    # 添加展开指示符(如果有内容)
                    if os.listdir(entry_path):
                        self.题库树.insert(node, "end", 
                                         text="点击展开...",
                                         values=("placeholder", ""))
                # 处理JSON文件           
                elif entry.endswith(".json"):
                    # 显示文件图标和去除扩展名的名称
                    self.题库树.insert(parent, "end", 
                                     text=f"&#128196; {os.path.splitext(entry)[0]}",
                                     values=("file", entry_path),
                                     tags=("file",))
        except PermissionError:
            print(f"无权限访问:{path}")
        except Exception as e:
            print(f"加载错误:{str(e)}")

    def 展开目录(self, event):
        """精准处理目录展开"""
        node = self.题库树.focus()
        node_type, path = self.题库树.item(node, "values")
        
        if node_type == "dir":
            # 删除旧加载项
            for child in self.题库树.get_children(node):
                self.题库树.delete(child)
            # 重新加载最新内容
            self.加载子题库(path, parent=node)

    def 选择题目库(self, event):
        """精准路径验证"""
        selected = self.题库树.selection()
        if not selected:
            return
        
        node = selected[0]
        node_type, path = self.题库树.item(node, "values")
        
        if node_type == "file":
            # 严格路径校验
            if not os.path.isfile(path):
                messagebox.showerror("错误", f"文件不存在:{path}")
                return
            if not path.lower().endswith(".json"):
                messagebox.showerror("错误", "仅支持.json格式文件")
                return
            
            try:
                with open(path, 'r', encoding='utf-8') as f:
                    self.当前题库 = json.load(f)
                # 记录完整路径而非显示名称
                self.当前题库路径 = path  
                self.创建题号导航面板(self.当前题库)
                self.创建题目组()
                self.显示题目()
            except json.JSONDecodeError:
                messagebox.showerror("格式错误", "JSON文件格式不正确")
            except Exception as e:
                messagebox.showerror("加载失败", str(e))

    def 刷新题库树(self):
        """刷新整个题库树"""
        for child in self.题库树.get_children():
            self.题库树.delete(child)
        self.题库树.insert("", "end", 
                          text="题库", 
                          iid=self.题库根路径, 
                          values=("dir", self.题库根路径), 
                          open=True)
        self.加载子题库(self.题库根路径, parent=self.题库根路径)
     
    def 加载做题记录(self):
        try:
            if os.path.exists(self.做题记录文件):
                with open(self.做题记录文件, 'r', encoding='utf-8') as f:
                    全记录 = json.load(f)
                    for 题库ID, 记录列表 in 全记录.items():
                        for 记录 in 记录列表:
                            self.题目状态[题库ID] = {
                                '状态': 记录.get('状态',''),
                                '正确次数': 记录.get('正确次数',0),
                                '错误次数': 记录.get('错误次数',0)
                            }
        except Exception as e:
            messagebox.showerror("错误", f"加载做题记录失败:{str(e)}")
    
    def 加载错题本(self):
        try:
            if os.path.exists(self.错题本文件):
                with open(self.错题本文件, 'r', encoding='utf-8') as f:
                    self.错题记录 = json.load(f)
                    # 兼容旧数据格式
                    for 错题 in self.错题记录:
                        if "剩余次数" not in 错题:
                            错题["剩余次数"] = 2
        except Exception as e:
            messagebox.showerror("错误", f"加载错题本失败:{str(e)}")
    
    def 保存做题记录(self):
        保存数据 = defaultdict(list)
        # 重组数据结构
        for key, value in self.题目状态.items():
            保存数据[key].append({
                "状态": value.get('状态',''),
                "正确次数": value.get('正确次数',0),
                "错误次数": value.get('错误次数',0)
                
            })
        # 确保数据目录存在
        if not os.path.exists("date"):
            os.makedirs("date")
        with open(self.做题记录文件, 'w', encoding='utf-8') as f:
            json.dump(保存数据, f, ensure_ascii=False)
    
    def 保存配置记录(self):
        保存数据 = {
            "config": {
                "auto_jump": self.自动跳转.get(),
                "show_answer": self.显示答案.get(),
                "auto_submit_single": self.自动提交单选.get()
            }
        }
        with open(self.配置文件, 'w', encoding='utf-8') as f:
            json.dump(保存数据, f, ensure_ascii=False)
        
    def 加载配置信息(self):    
        if os.path.exists(self.配置文件):
            with open(self.配置文件, 'r') as f:
                数据 = json.load(f)
                if "config" in 数据:
                    self.自动跳转.set(数据["config"].get("auto_jump", True))
                    self.显示答案.set(数据["config"].get("show_answer", True))
                    self.自动提交单选.set(数据["config"].get("auto_submit_single", True))
                
    def 加载当前题库记录(self):
        self.题目状态 = defaultdict(dict)
        try:
            if os.path.exists(self.做题记录文件):
                with open(self.做题记录文件, 'r', encoding='utf-8') as f:
                    全记录 = json.load(f)
                    # 过滤当前题库记录
                    for key in 全记录:
                        if key.startswith(f"{self.当前题库名称}:"):
                            题目id = key.split(":")[1]
                            self.题目状态[题目id] = 全记录[key]
        except Exception as e:
            print(f"加载记录失败:{str(e)}")
    
    def 保存错题本(self):
        with open(self.错题本文件, 'w', encoding='utf-8') as f:
            json.dump(self.错题记录, f, ensure_ascii=False, indent=2)

    def 显示模式按钮(self):
        """根据模式显示对应按钮"""
        # 先隐藏所有按钮
        for btn in self.作答区.winfo_children():
            btn.pack_forget()
        
        if self.多题模式:
            self.上一页按钮.pack(side="left", padx=5)
            self.页数标签.pack(side="left", padx=5)
            self.下一页按钮.pack(side="left", padx=5)
            self.提交按钮.pack(side="left", padx=5)
        else:
            self.上一题按钮.pack(side="left", padx=5)
            
            self.下一题按钮.pack(side="left", padx=5)
            self.提交按钮.pack(side="left", padx=5)
    
    def 切换背题模式(self):
        """完善背题模式的控件状态管理"""
        if self.背题模式.get():
            for idx, card in enumerate(self.题目卡片组):
                题目 = self.当前题库[idx]
                correct_answer = set(题目["answer"].upper())
                # 更新反馈标签
                card.feedback.config(
                    text=f"正确答案:{题目['answer'].upper()}",
                    fg="green"
                )
                for widget in card.option_widgets:
                    opt_idx = card.option_widgets.index(widget)
                    option_char = chr(65 + opt_idx)
                    
                    # 设置正确答案标记
                    if option_char in correct_answer:
                        if not widget["correct_mark"]:
                            # 正确写法:获取选项行的父容器作为主控件
                            mark = tk.Label(widget["label"].master,  # 修复父容器
                                          text="&#10003;",
                                          fg="#2E8B57",
                                          font=("宋体", self.字体大小, "bold"))
                            mark.pack(side=tk.RIGHT)
                            widget["correct_mark"] = mark
                        
                        # 高亮正确选项文字
                        widget["label"].config(fg="#2E8B57")
                        
                    # 禁用所有交互控件
                    if widget.get("radio"):
                        widget["radio"].config(state=tk.DISABLED)
                        # 自动选中正确答案
                        if option_char in correct_answer:
                            widget["radio"].select()
                    if widget.get("check"):
                        widget["check"].config(state=tk.DISABLED)
                        if option_char in correct_answer:
                            widget["check"].select()
                    
                    # 移除标签的点击事件绑定
                    widget["label"].unbind("<Button-1>")
        else:
            # 退出背题模式时的恢复逻辑
            for idx, card in enumerate(self.题目卡片组):
                题目 = self.当前题库[idx]
                # 更新反馈标签
                card.feedback.config(text= "")
                for widget in card.option_widgets:
                    # 恢复文字颜色
                    widget["label"].config(fg="black")
                    
                    # 移除正确答案标记
                    if widget["correct_mark"]:
                        widget["correct_mark"].destroy()
                        widget["correct_mark"] = None
                    
                    # 恢复控件状态
                    if widget.get("radio"):
                        widget["radio"].deselect()
                        widget["radio"].config(state=tk.NORMAL)
                        #widget["radio"].set('')
                        var = self.用户答案[题目["id"]]
                        if isinstance(var, tk.StringVar):
                            var.set(False)  # 清空单选按钮变量
                        
                    if widget.get("check"):
                        widget["check"].config(state=tk.NORMAL)
                        widget["check"].deselect()
                    
                    # 重新绑定点击事件
                    题目 = self.当前题库[self.题目卡片组.index(card)]
                    is_multi = len(题目["answer"]) >1
                    widget["label"].bind("<Button-1>", 
                                       self.创建选项点击事件(题目, 
                                                         card.option_widgets.index(widget),
                                                         is_multi))
        self.显示题目()
            
    def 切换显示模式(self):
        
        self.跳转题目(0)
        # 切换显示模式
        self.多题模式 = not self.多题模式
        self.当前页数 = 1  # 重置为第一页
        self.总页数 = (len(self.当前题库) + self.每页题数 - 1) // self.每页题数
        # 更新按钮显示
        self.显示模式按钮()
        self.当前显示卡片索引 = 0  # 确保切换到多题模式时从第一题开始
        for card in enumerate(self.题目卡片组):
            if hasattr(card, 'submit_container'):
                if self.多题模式:
                    card.submit_container.pack_forget()
                else:
                    # 触发状态检查
                    qid = self.当前题库[self.题目卡片组.index(card)]["id"]
                    if isinstance(self.用户答案[qid], dict):  # 多选
                        self.更新提交按钮状态(card, self.用户答案[qid])
        self.模式切换按钮.config(text="切换单题模式" if self.多题模式 else "切换多题模式")
        
        self.显示题目()
        self.调整布局()
        # 分页按钮状态更新
        self.更新分页按钮状态()
         # 延迟确保布局完成
        def 延迟滚动():
            # 三重保障机制
            self.当前显示卡片索引 = 0
            self.多题容器.yview_moveto(0)  # 滚动到最顶部
            self.多题容器.event_generate("<Configure>")  # 强制触发布局计算
            self.主窗口.update_idletasks()
            
        if self.多题模式 and self.多题容器:
            self.主窗口.after(150, 延迟滚动)  # 150ms延迟适应不同性能环境
    
    def 更新分页按钮状态(self):
        """控制分页按钮可用状态"""
        if self.多题模式:
            self.上一页按钮["state"] = tk.NORMAL if self.当前页数 > 1 else tk.DISABLED
            self.页数标签["text"] = f"{self.当前页数}/{self.总页数}"
            self.下一页按钮["state"] = tk.NORMAL if self.当前页数 < self.总页数 else tk.DISABLED    

    def 切换页数(self, 增量):
        new_page = self.当前页数 + 增量
        if 1 <= new_page <= self.总页数:
            self.当前页数 = new_page
            self.显示题目()
            self.更新分页按钮状态()

    def 显示题目(self):
        # 空题库处理
        if not self.题目卡片组:
            return
        # 索引越界保护
        if self.当前显示卡片索引 >= len(self.当前题库):
            self.当前显示卡片索引 = 0
        if self.多题模式:
            # 计算当前页题目范围
            start = (self.当前页数 -1) * self.每页题数
            end = start + self.每页题数
            #当前页题目 = self.当前题库[start:end]
            # 隐藏所有卡片
            for card in self.题目卡片组:
                card.pack_forget() 
            
            # 显示当前页卡片
            for idx in range(start, min(end, len(self.当前题库))):
                self.题目卡片组[idx].pack(fill=tk.X, pady=5, padx=5)

            # 滚动到顶部
            self.多题容器.yview_moveto(0)
            self.滚动条.grid()
            self.更新分页按钮状态()
        else:
            for idx, card in enumerate(self.题目卡片组):
                if idx == self.当前显示卡片索引:
                    # 单题模式填充整个空间
                    card.pack(fill=tk.BOTH, expand=True, padx=0, pady=10)
                    card.update_idletasks()  # 关键:立即应用布局变化
                    self.调整布局()  # 首次显示时触发布局计算
                else:
                    card.pack_forget()
            self.滚动条.grid_remove()
    
    def 调整布局(self, event=None):
        # 空数据校验
        if not self.题目卡片组 or self.当前显示卡片索引 >= len(self.题目卡片组):
            return
        # 获取右侧容器完整宽度
        container_width = self.右侧容器.winfo_width()
        # 统一设置所有卡片宽度
        for 卡片 in self.题目卡片组:
            卡片.config(width=container_width)  # 固定卡片宽度            
            # 题目标签填满容器
            title_label = 卡片.winfo_children()[0]
            title_label.config(wraplength=container_width-20)  # 保留边距但允许自动换行
            # 选项容器填满宽度
            选项容器 = 卡片.winfo_children()[1]
            选项容器.config(width=container_width)
            for 选项行 in 选项容器.winfo_children():
                选项行.config(width=container_width)
                if len(选项行.winfo_children()) > 1:
                    # 选项标签强制填满剩余空间
                    lbl = 选项行.winfo_children()[1]
                    lbl.config(wraplength=container_width - 50 )  # 根据按钮宽度调整
        
    def 创建题目组(self):   
        if hasattr(self, '多题容器') and self.多题容器:
            self.多题容器.destroy()
            self.多题容器 = None
            
        if hasattr(self, '滚动条') and self.滚动条:
            self.滚动条.destroy()
            self.滚动条 = None  
        self.滚动条 = ttk.Scrollbar(self.右侧容器, orient="vertical")
        self.配置滚动条样式()
        # 创建通用容器
        self.多题容器 = tk.Canvas(self.右侧容器, bg='white', highlightthickness=0,yscrollcommand=self.滚动条.set)  
        self.滚动条.configure(command=self.多题容器.yview)     
        # 初始隐藏滚动条
        self.滚动条.grid(row=0, column=1, sticky="ns")
        self.滚动条.grid_remove()  # 默认隐藏
        self.多题容器.grid(row=0, column=0, sticky="nsew")
        self.右侧容器.grid_rowconfigure(0, weight=1)
        self.右侧容器.grid_columnconfigure(0, weight=1)        
        # 创建内部框架
        self.内层框架 = tk.Frame(self.多题容器)
        self.多题容器.create_window((0,0), window=self.内层框架, anchor="nw")        
        # 生成所有题目卡片(始终存在)
        self.题目卡片组 = []
        for idx, 题目 in enumerate(self.当前题库):
            卡片 = self.创建题目卡片(题目, idx)
            卡片.pack_forget()  # 初始全部隐藏
            self.题目卡片组.append(卡片)
        # 初始化分页
        self.总页数 = (len(self.当前题库) + self.每页题数 -1) // self.每页题数
        self.更新分页按钮状态()
        self.内层框架.bind("<Configure>", self.更新滚动区域)
        # 绑定鼠标滚轮事件
        self.多题容器.bind("<MouseWheel>", self._处理鼠标滚动)
        self.内层框架.bind("<MouseWheel>", self._处理鼠标滚动)
        # 为滚动条添加额外绑定(增强兼容性)
        self.滚动条.bind("<MouseWheel>", self._处理鼠标滚动)
        # 设置滚动步长(影响滑块大小)
        self.多题容器.configure(yscrollincrement=40)  # 每步滚动40像素
    
    def 配置滚动条样式(self):
        """使用ttk样式配置滚动条外观"""
        style = ttk.Style()
        # 定义滚动条元素布局
        style.layout("Vertical.TScrollbar",
                   [('Vertical.Scrollbar.trough', {'children':
                    [('Vertical.Scrollbar.thumb', {'expand': '1', 'sticky': 'nswe'})],
                    'sticky': 'ns'})])        
        # 配置颜色和尺寸
        style.configure("Vertical.TScrollbar",
                      troughcolor="#f0f0f0",
                      bordercolor="#c0c0c0",
                      arrowcolor="#808080",
                      gripcount=0,
                      arrowsize=14,  # 箭头大小
                      sliderlength=40,  # 滑块长度
                      thickness=14)  # 滚动条宽度
        
        # 应用样式
        self.滚动条.configure(style="Vertical.TScrollbar")
    
    def 更新滚动区域(self, event):
        """优化后的滚动区域更新"""
        # 计算内容总高度
        内容高度 = self.内层框架.winfo_height()
        容器高度 = self.多题容器.winfo_height()
        
        # 更新滚动区域
        self.多题容器.configure(
            scrollregion=self.多题容器.bbox("all"),
            yscrollincrement=容器高度*0.1  # 按容器高度的10%步进
        )
        
    def _处理鼠标滚动(self, event):
        """通用鼠标滚动处理"""
        if not self.多题模式:
            return
        # Windows/Mac的delta值处理
        if event.delta:
            units = -1 * (event.delta // 120)
        # Linux的按钮事件处理
        else:
            units = -1 if event.num == 4 else 1
            
        self.多题容器.yview_scroll(units, "units")
        
        # 限制滚动范围
        y0, y1 = self.多题容器.yview()
        if y0 < 0: 
            self.多题容器.yview_moveto(0)
        elif y1 > 1: 
            self.多题容器.yview_moveto(1)
        return "break"  # 阻止事件继续传播

    def 更新提交按钮状态(self, 卡片框, vars_dict):
        """根据选项选择情况更新提交按钮可见性"""
        has_selection = any(var.get() for var in vars_dict.values())
        if has_selection and self.自动提交单选.get() :
            卡片框.submit_container.pack(side="left",pady=5)
        else:
            卡片框.submit_container.pack_forget()
            
    def 创建题目卡片(self, 题目, 索引):
        卡片框 = tk.LabelFrame(self.内层框架, 
                            text=f"第{索引+1}题", 
                            padx=0, 
                            pady=10,
                            width=self.右侧容器.winfo_width())  # 初始宽度同步   
        # 为卡片框绑定滚轮事件
        卡片框.bind("<MouseWheel>", self._处理鼠标滚动)
        卡片框.option_widgets = []  # 初始化选项控件列表
        # 内容标签配置修改
        内容标签 = tk.Label(
            卡片框, 
            text=题目["title"],
            font=("宋体", self.字体大小),
            anchor="nw",
            justify="left",
            wraplength=self.右侧容器.winfo_width(), #-10,  # 初始值同步容器
            width=self.右侧容器.winfo_width()  # 关键修改:固定标签宽度
        )
        内容标签.pack(anchor="nw", fill=tk.X, expand=True)
        # 选项容器配置
        选项容器 = tk.Frame(卡片框, width=self.右侧容器.winfo_width())
        选项容器.pack(fill=tk.BOTH, expand=True)
        # 创建提交按钮容器(初始隐藏)
        提交容器 = tk.Frame(卡片框)
        提交容器.pack_forget()  # 初始隐藏

        # 答案存储变量
        is_multi = len(题目["answer"]) > 1
        if is_multi:
            # 多选题使用字典记录每个选项状态
            vars_dict = {chr(65+i): tk.BooleanVar() for i in range(len(题目["option"]))}
            self.用户答案[题目["id"]] = vars_dict
            提交按钮 = tk.Button(
                提交容器,
                text="提交答案",
                command=lambda: self.自动提交作答()
            )
            提交按钮.pack(pady=5)
            # 存储提交按钮引用
            卡片框.submit_container = 提交容器
            卡片框.submit_button = 提交按钮
            # 监控选项变更
            for var in vars_dict.values():
                var.trace_add("write", lambda *args: self.更新提交按钮状态(卡片框, vars_dict))
        else:
            # 单选题使用单个变量
            var = tk.StringVar()
            self.用户答案[题目["id"]] = var
            var.set(False)  # 初始化为未选择
        # 动态创建选项
        for opt_idx, 选项 in enumerate(题目["option"]):
            选项行 = tk.Frame(选项容器, width=self.右侧容器.winfo_width())
            选项行.pack(fill=tk.X, expand=True, anchor="nw", pady=2)
            # 创建控件并存储引用
            widget_data = {}
            if is_multi:
                # 多选题复选框
                cb = tk.Checkbutton(选项行,
                                variable=vars_dict[chr(65+opt_idx)],
                                command=lambda k=chr(65+opt_idx): self.更新多选答案(题目["id"], k))
                
                cb.pack(side="left")
                widget_data["check"] = cb

            else:
                # 单选题单选按钮
                rb = tk.Radiobutton(选项行,
                                variable= var,
                                value=chr(65+opt_idx),
                                command=lambda v=chr(65+opt_idx): self.处理单选自动提交(题目, v),
                                indicatoron=1)
                
                rb.pack(side="left")
                widget_data["radio"] = rb
        
            # 选项文本(带点击事件绑定)
            lbl = tk.Label(选项行, 
                text=f"{选项}",
                font=("宋体", self.字体大小),
                anchor="nw",
                justify="left",  
                wraplength=self.右侧容器.winfo_width()-50,
                width=self.右侧容器.winfo_width()-50  # 留出按钮空间
            )
            lbl.bind("<Button-1>", self.创建选项点击事件(题目, opt_idx, is_multi))
            lbl.pack(side=tk.LEFT, fill=tk.X, expand=True)
            widget_data["label"] = lbl
            widget_data["correct_mark"] = None
            # 存储控件引用到卡片对象
            卡片框.option_widgets.append(widget_data) 
        # 反馈标签
        feedback = tk.Label(卡片框, text="", font=("宋体", self.字体大小))
        feedback.pack(anchor="nw")        
        # 保存反馈引用
        卡片框.feedback = feedback
        # 递归绑定所有子部件
        def 递归绑定滚轮(widget):
            widget.bind("<MouseWheel>", self._处理鼠标滚动)
            for child in widget.winfo_children():
                递归绑定滚轮(child)
        
        递归绑定滚轮(卡片框)     
        return 卡片框
    
    def 处理单选自动提交(self, 题目, 选项值):
        self.用户答案[题目["id"]].set(选项值)
        if self.自动提交单选.get() and not self.多题模式:
            self.自动提交作答()
        if self.自动跳转.get() and not self.多题模式:
            self.主窗口.after(500, self.下一题)

    def 创建选项点击事件(self, 题目, opt_idx, is_multi):
        def 点击处理(event):
            if is_multi:
                current = self.用户答案[题目["id"]][chr(65+opt_idx)].get()
                self.用户答案[题目["id"]][chr(65+opt_idx)].set(not current)
            else:
                self.处理单选自动提交(题目,chr(65+opt_idx))

        return 点击处理    
    # 提交答案
    def 提交所有答案(self):
        """背题模式禁用提交功能"""
        if self.背题模式.get():
            messagebox.showinfo("提示", "当前为背题模式,无法提交答案")
            return
        是否提交 = messagebox.askyesno(
            "是否提交",
            "确认提交请点'是',否则点'否'",
            parent=self.主窗口
        )
        if 是否提交:
            correct_count = 0
            total_answered = 0
            for 题目 in self.当前题库:
                qid = 题目["id"]
                用户选择 = ""            
                # 获取用户答案
                if isinstance(self.用户答案[qid], tk.StringVar):  # 单选题
                    用户选择 = self.用户答案[qid].get()
                else:  # 多选题
                    用户选择 = "".join([k for k, v in self.用户答案[qid].items() if v.get()])           
                # 跳过未作答题目
                if not 用户选择:
                    continue                
                total_answered += 1
                正确答案 = 题目["answer"].upper()
                是否正确 = (set(用户选择) == set(正确答案)) if len(正确答案)>1 else (用户选择 == 正确答案)
                            
                # 更新反馈
                for card in self.题目卡片组:
                    if card.winfo_children()[0].cget("text").startswith(题目["title"][:10]):
                        color = "green" if 是否正确 else "red"
                        symbol = "&#10003;" if 是否正确 else "&#10007;"
                        self.更新做题信息(是否正确, 题目)
                        card.feedback.config(
                            text=f"{symbol} 你的答案:{用户选择},正确答案:{正确答案}",
                            fg=color
                        )
                        if 是否正确:
                            correct_count += 1
                        break        
            # 显示统计结果
            messagebox.showinfo("答题结果",
                            f"已答题目:{total_answered}\n正确率:{correct_count/total_answered:.1%}"
                            if total_answered else "请先作答题目!")
            self.保存做题记录()
            self.更新题号样式()
            self.保存错题本()
            self.主窗口.update_idletasks()  # 强制刷新界面

    def 自动提交作答(self):
        """单题提交逻辑"""
        题目 = self.当前题库[self.当前显示卡片索引]
        qid = 题目["id"]
        用户选择 = self.获取当前卡片答案(qid)        
        # 验证逻辑
        正确答案 = 题目["answer"].upper()
        是否正确 = (set(用户选择) == set(正确答案)) if len(正确答案)>1 else (用户选择 == 正确答案)        
        # 更新反馈
        feedback = self.题目卡片组[self.当前显示卡片索引].feedback
        color = "green" if 是否正确 else "red"
        symbol = "&#10003;" if 是否正确 else "&#10007;"
        self.更新做题信息(是否正确, 题目)
        if self.显示答案.get():
            feedback_text = f"{symbol} 你的答案:{用户选择},正确答案:{正确答案}"
            feedback.config(text=feedback_text, fg=color)
        else:
            feedback_text = symbol
        
        self.保存做题记录()
        self.更新题号样式()
        self.保存错题本()

        if self.自动跳转.get() and not self.多题模式:
            self.主窗口.after(500, self.下一题)
        self.主窗口.update_idletasks()  # 强制刷新界面

    def 更新多选答案(self, 题目ID, 选项):
        # 自动更新字典状态,无需额外操作
        pass    
    
    def 刷新当前卡片显示(self, 题目):
        """编辑后刷新卡片"""
        idx = self.当前显示卡片索引
        # 销毁旧卡片
        self.题目卡片组[idx].destroy()
        # 创建新卡片
        new_card = self.创建题目卡片(题目, idx)
        new_card.pack_forget()
        self.题目卡片组[idx] = new_card
        # 重新显示
        self.跳转题目(idx)
        
    def 创建题号导航面板(self, 题库数据, 是否是错题=False):
        """导航面板"""
        def 鼠标滚动处理(event):
            if not self.允许滚动:  # 新增判断条件
                return
            # 优化滚动灵敏度(提高3倍滚动速度)
            units = -1 * (event.delta // 30)  # Windows/Linux标准单位
            画布.yview_scroll(units, "units")
            
            # 限制滚动范围
            y0, y1 = 画布.yview()
            if y0 < 0: 
                画布.yview_moveto(0)
            elif y1 > 1: 
                画布.yview_moveto(1)

        if self.导航框:
            self.导航框.destroy()
        
        # 生成标题
        标题 = "错题导航" if 是否是错题 else f"{self.当前题库名称}导航"
        总题数 = len(题库数据)
        
        # 创建导航框架
        self.导航框 = tk.LabelFrame(self.中间面板, text=f"{标题}({总题数}题)", padx=5, pady=5)
        self.导航框.pack(fill=tk.BOTH, expand=True, pady=10)
        
        # 创建滚动区域
        画布 = tk.Canvas(self.导航框, borderwidth=0, highlightthickness=0)
        滚动条容器 = tk.Frame(self.导航框, width=15)  # 固定宽度避免闪烁
        滚动条容器.pack(side="right", fill="y")
        
        滚动条 = tk.Scrollbar(滚动条容器, orient="vertical", command=画布.yview)
        滚动条.pack(expand=True, fill="y")
        
        滚动容器 = tk.Frame(画布)
        
        # 配置画布滚动
        画布.configure(yscrollcommand=滚动条.set)
        画布.pack(side="left", fill="both", expand=True)
        
        # 将滚动容器放入画布
        画布.create_window((0,0), window=滚动容器, anchor="nw")
        
        # 配置网格布局
        滚动容器.grid_rowconfigure(0, weight=1)
        滚动容器.grid_columnconfigure(0, weight=1)
        
        # 生成题号按钮
        self.题号按钮组 = []
        行号 = 0
        for i in range(len(题库数据)):
            列号 = i % 9  # 每行9个按钮
            if i > 0 and i % 9 == 0:
                行号 += 1
                
            按钮 = tk.Button(
                滚动容器,
                text=str(i+1),
                width=2,
                relief="groove",
                command=lambda idx=i: self.跳转题目(idx)
            )
            按钮.grid(row=行号, column=列号, padx=1, pady=1, sticky="nsew")
            按钮.bind("<MouseWheel>", 鼠标滚动处理)
            self.题号按钮组.append(按钮)
        
        # 智能显示滚动条
        def 更新滚动可见性():
            容器高度 = 画布.winfo_height()
            内容高度 = 滚动容器.winfo_reqheight()
            
            # 精确判断是否需要滚动(误差控制在5像素内)
            self.允许滚动 = 内容高度 > 容器高度 + 5  # 新增状态标志
            
            if self.允许滚动:
                # 显示滚动条并启用滚动
                滚动条容器.pack(side="right", fill="y")
                画布.configure(yscrollcommand=滚动条.set)
                # 设置精确的滚动区域
                画布.configure(scrollregion=(
                    0, 0, 
                    滚动容器.winfo_reqwidth(), 
                    max(内容高度, 容器高度)  # 确保可滚动区域
                ))
            else:
                # 隐藏滚动条并禁用滚动
                滚动条容器.pack_forget()
                画布.configure(yscrollcommand=None)
                # 固定滚动区域为可视区域
                画布.configure(scrollregion=(
                    0, 0, 
                    滚动容器.winfo_reqwidth(), 
                    内容高度
                ))
        
        # 配置滚动容器自动扩展
        滚动容器.update_idletasks()
        画布.configure(scrollregion=画布.bbox("all"))
        
        # 绑定事件
        画布.bind("<Configure>", lambda e: (更新滚动可见性(), 画布.configure(scrollregion=画布.bbox("all"))))
        滚动容器.bind("<Configure>", lambda e: 更新滚动可见性())
        画布.bind("<MouseWheel>", 鼠标滚动处理)
        滚动容器.bind("<MouseWheel>", 鼠标滚动处理)
        self.允许滚动 = False
        # 初始更新
        更新滚动可见性()
        
        # 更新按钮样式
        self.更新题号样式()
    
    def 更新题号样式(self):
        for i, 题目 in enumerate(self.当前题库):
            按钮 = self.题号按钮组[i]
            # 正常模式状态显示
            记录ID = f"{题目['id']}"
            记录 = self.题目状态.get(记录ID, {})
            # 排除当前题号(单独处理)
            if i == self.当前显示卡片索引:
                continue    
            # 设置颜色和提示
            if 记录.get('状态') == '正确':
                按钮.config(bg='#89CFF0')
            elif 记录.get('状态') == '错误':
                按钮.config(bg='#FF6961')
            else:
                按钮.config(bg='SystemButtonFace', fg='SystemWindowText')
        
    def 获取当前卡片答案(self, qid):
        """获取指定卡片的答案"""
        answer_var = self.用户答案[qid]
        if isinstance(answer_var, tk.StringVar):  # 单选题
            return answer_var.get()
        else:  # 多选题
            return "".join([k for k, v in answer_var.items() if v.get()])        
    
    def 跳转题目(self, 题号索引):
        if 0 <= 题号索引 < len(self.当前题库):
            self.当前显示卡片索引 = 题号索引
            self.显示题目()
            if self.多题模式:
                # 多题模式滚动到指定位置
                self.多题容器.yview_moveto(题号索引/len(self.当前题库))
                #messagebox.showinfo("提示", "该模式下无法进行跳转!")
                return
            else:
                # 单题模式更新题号样式
                self.更新题号样式()
        
    def 更新做题信息(self, 是否正确, 题目):        
        # 错题模式处理
        if self.错题模式:
            # 找到对应的错题记录
            错题条目 = next((q for q in self.错题记录 if q["id"] == 题目["id"]), None)
            if 错题条目:
                if 是否正确:
                    错题条目["剩余次数"] -= 1
                    if 错题条目["剩余次数"] <= 0:
                        self.错题记录.remove(错题条目)
                        # 从当前题库移除
                        self.当前题库.pop(self.当前显示卡片索引)
                        self.显示错题本()
                        if self.当前显示卡片索引 >= len(self.当前题库):
                            self.当前显示卡片索引 = 0
                else:
                    错题条目["剩余次数"] = 2  # 答错重置次数
                # 更新导航面板
                self.创建题号导航面板(self.当前题库, 是否是错题=True)
        else: #正常模式
            key = f"{题目['id']}"
            if not 是否正确:
                self.题目状态[key]['状态'] = '错误'
                self.题目状态[key]['错误次数'] = self.题目状态[key].get('错误次数', 0) + 1
                # 记录完整错题信息
                新错题 = 题目.copy()
                新错题.update({
                    "剩余次数": 2,
                    "来源题库": self.当前题库名称
                })
                # 去重处理
                if not any(q["id"] == 新错题["id"] for q in self.错题记录):
                    self.错题记录.append(新错题)
                    #self.保存错题本()
            else:
                self.题目状态[key]['状态'] = '正确'
                self.题目状态[key]['正确次数'] = self.题目状态[key].get('正确次数', 0) + 1
                #self.主窗口.after(500, self.自动下一题)

    def 自动下一题(self):
        if self.当前显示卡片索引 < len(self.当前题库)-1:
            self.当前显示卡片索引 += 1
        else:
            self.当前显示卡片索引 = 0
        self.显示题目()
        self.更新题号样式()

    def 退出错题模式(self):
        # 恢复原状态时包括分页信息
        (self.当前题库, 
         self.当前显示卡片索引, 
         self.当前题库名称,
         self.当前页数) = self.原状态备份
        self.错题模式 = False
        # 强制重新创建题目容器
        self.创建题目组()  # 关键修复:恢复原题库卡片
        
        # 更新界面元素
        self.创建题号导航面板(self.当前题库)
        self.显示题目()
        self.调整布局()
        self.错题按钮.config(text="错题", command=self.显示错题本)

    def 上一题(self):
        if self.当前显示卡片索引 > 0 :
            self.跳转题目(self.当前显示卡片索引 - 1)

    def 下一题(self):
        if self.当前显示卡片索引 < len(self.当前题库)-1:
            self.跳转题目(self.当前显示卡片索引 + 1)
    
    def 显示错题本(self):
        if not self.错题记录:
            messagebox.showinfo("提示", "当前没有需要练习的错题")
            return
        self.原状态备份 = (self.当前题库.copy(), self.当前显示卡片索引, self.当前题库名称,self.当前页数)
        self.错题模式 = True
        self.当前题库 = [q.copy() for q in self.错题记录]  # 使用副本避免修改原数据
        self.当前题库名称 = "错题本"
        self.创建题目组()
        self.当前页数 = 1
        self.当前显示卡片索引 = 0
        self.创建题号导航面板(self.当前题库, 是否是错题=True)
        self.显示题目()
        self.调整布局()
        self.错题按钮.config(text="返回正常模式", command=self.退出错题模式)
        
    def 调整字体大小(self, 增量):
        new_size = self.字体大小 + 增量
        if 10 <= new_size <= 24:
            self.字体大小 = new_size
            
            # 定义递归更新函数
            def 更新字体(widget):
                # 更新当前组件
                if isinstance(widget, (tk.Label, tk.Radiobutton, tk.Checkbutton)):
                    widget.config(font=("宋体", self.字体大小))
                # 递归处理子组件
                for child in widget.winfo_children():
                    更新字体(child)
            
            # 遍历所有卡片
            for card in self.题目卡片组:
                更新字体(card)
                
            # 强制刷新界面
            self.主窗口.update_idletasks()            
       
    def 编辑题目(self): # 编辑题目的方法
        if self.多题模式:
            messagebox.showwarning("提示", "多题模式下无法编辑!")
            return
        if not self.当前题库 or self.当前显示卡片索引 >= len(self.当前题库):
            messagebox.showwarning("提示", "请先选择题目")
            return
        当前题目 = self.当前题库[self.当前显示卡片索引]
        self.弹出编辑对话框(当前题目)

    def 弹出编辑对话框(self, 题目):
        对话框 = tk.Toplevel(self.主窗口)
        对话框.title("编辑题目")
        对话框.grab_set()  # 设置为模态对话框
        
        # 题目内容
        tk.Label(对话框, text="题目内容:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        题目内容输入 = tk.Text(对话框, wrap=tk.WORD, width=60, height=4)
        题目内容输入.insert(tk.END, 题目.get('title', ''))
        题目内容输入.grid(row=0, column=1, columnspan=3, padx=5, pady=5)
        
        # 选项部分
        tk.Label(对话框, text="选项:").grid(row=1, column=0, sticky="nw", padx=5, pady=5)
        option_frame = tk.Frame(对话框)
        option_frame.grid(row=1, column=1, columnspan=3, sticky="ew")
        
        option_frames = []  # 存储所有选项行的Frame
        
        # 初始化现有选项
        for i, opt in enumerate(题目.get('option', [])):
            frame = tk.Frame(option_frame)
            frame.pack(fill=tk.X, pady=2)
            label = tk.Label(frame, text=f"选项{chr(65 + i)}:")
            label.pack(side=tk.LEFT)
            entry = tk.Entry(frame)
            entry.insert(0, opt)
            entry.pack(side=tk.LEFT, expand=True, fill=tk.X)
            # 删除按钮
            def 删除处理(f=frame):
                f.destroy()
                option_frames.remove(f)
                # 更新剩余选项的标签
                for idx, fr in enumerate(option_frames):
                    fr_label = fr.winfo_children()[0]
                    fr_label.config(text=f"选项{chr(65 + idx)}:")
            del_btn = tk.Button(frame, text="×", command=lambda f=frame: 删除处理(f))
            del_btn.pack(side=tk.RIGHT)
            option_frames.append(frame)
        
        # 添加新选项按钮
        def 添加新选项():
            frame = tk.Frame(option_frame)
            frame.pack(fill=tk.X, pady=2)
            当前选项数 = len(option_frames)
            label = tk.Label(frame, text=f"选项{chr(65 + 当前选项数)}:")
            label.pack(side=tk.LEFT)
            entry = tk.Entry(frame)
            entry.pack(side=tk.LEFT, expand=True, fill=tk.X)
            # 删除按钮
            def 删除处理(f=frame):
                f.destroy()
                option_frames.remove(f)
                for idx, fr in enumerate(option_frames):
                    fr_label = fr.winfo_children()[0]
                    fr_label.config(text=f"选项{chr(65 + idx)}:")
            del_btn = tk.Button(frame, text="×", command=lambda f=frame: 删除处理(f))
            del_btn.pack(side=tk.RIGHT)
            option_frames.append(frame)
        
        add_btn = tk.Button(对话框, text="添加选项", command=添加新选项)
        add_btn.grid(row=2, column=1, pady=5)
        
        # 正确答案
        tk.Label(对话框, text="正确答案:").grid(row=3, column=0, sticky="w", padx=5, pady=5)
        answer_var = tk.StringVar(value=题目.get('answer', ''))
        answer_entry = tk.Entry(对话框, textvariable=answer_var)
        answer_entry.grid(row=3, column=1, columnspan=3, sticky="ew", padx=5, pady=5)
        
        # 解析
        tk.Label(对话框, text="解析:").grid(row=4, column=0, sticky="w", padx=5, pady=5)
        analysis_input = tk.Text(对话框, wrap=tk.WORD, width=60, height=4)
        analysis_input.insert(tk.END, 题目.get('analysis', ''))
        analysis_input.grid(row=4, column=1, columnspan=3, padx=5, pady=5)
        
        def 保存修改():
            new_title = 题目内容输入.get("1.0", tk.END).strip()
            if not new_title:
                messagebox.showerror("错误", "题目内容不能为空")
                return
            
            new_options = []
            for frame in option_frames:
                entry = frame.winfo_children()[1]  # 获取Entry控件
                opt_text = entry.get().strip()
                if opt_text:
                    new_options.append(opt_text)
            if not new_options:
                messagebox.showerror("错误", "至少需要一个选项")
                return
            
            new_answer = answer_var.get().strip().upper()
            if not new_answer:
                messagebox.showerror("错误", "答案不能为空")
                return
            
            valid_chars = set(chr(65 + i) for i in range(len(new_options)))
            for char in new_answer:
                if char not in valid_chars:
                    messagebox.showerror("错误", f"答案 '{char}' 无效,有效选项为 {', '.join(valid_chars)}")
                    return
            
            题目['title'] = new_title
            题目['option'] = new_options
            题目['answer'] = new_answer
            题目['analysis'] = analysis_input.get("1.0", tk.END).strip()
            
            try:
                with open(self.当前题库路径, 'w', encoding='utf-8') as f:
                    json.dump(self.当前题库, f, ensure_ascii=False, indent=2)
                messagebox.showinfo("成功", "题目已保存")
                self.刷新当前卡片显示(题目)  # 刷新当前题目显示
                对话框.destroy()
            except Exception as e:
                messagebox.showerror("保存失败", f"无法保存文件:{str(e)}")
        
        button_frame = tk.Frame(对话框)
        button_frame.grid(row=5, column=0, columnspan=4, pady=10)
        tk.Button(button_frame, text="保存", command=保存修改).pack(side=tk.LEFT, padx=5)
        tk.Button(button_frame, text="取消", command=对话框.destroy).pack(side=tk.LEFT, padx=5)
        
        对话框.columnconfigure(1, weight=1)
        for i in range(5):
            对话框.rowconfigure(i, weight=0)
    # 导入题目方法
    def 导入题目(self):
        file_path = filedialog.askopenfilename(
            parent=self.主窗口,
            filetypes=[('文本文件', '*.txt')],
            title="选择题目文件"
        )
        if not file_path:
            return

        # 创建模式选择对话框
        mode = messagebox.askyesno(
            "模式选择",
            "答案在每题后请点'是',答案在末尾请点'否'",
            parent=self.主窗口
        )
        
        try:
            with open(file_path, 'r', encoding='UTF-8') as f:
                data = f.read().replace('.', '.')
            
            result = []
            题库目录 = "题库"
            
            if mode:
                # 模式1:答案在题目后
                pattern = re.compile(r'(?:^|\n\s*)\d+?[\.。]')
                questions = pattern.split(data)
                for q in questions:
                    if not q.strip():
                        continue
                    lines = re.split(r'\n', q, 1)
                    title = lines[0] if len(lines) > 0 else ''
                    content = lines[1] if len(lines) > 1 else ''
                    
                    # 提取选项
                    options = re.findall(r'[A-E][.。]?(.+?)(?=\s+[A-E][.。]|\s*$)', re.split(r'答案[::]',content)[0], re.DOTALL)
                    # 提取答案和解析
                    answer_match = re.search(r'答案[::]([A-E]+)', content)
                    analysis_match = re.search(r'解析[::]([\s\S]+)', content)
                    
                    answer = answer_match.group(1) if answer_match else ''
                    analysis = analysis_match.group(1).strip() if analysis_match else ''   
                    result.append({
                        "id": f"{time.strftime('%Y%m%d%H%M')}{random.randint(0,1000000)}",
                        "title": title,
                        "option": [o.strip() for o in options if o.strip()],
                        "answer": answer,
                        "analysis": analysis
                    })
            else:
                # 模式2:答案在末尾
                answer_section = re.search(r'答案[::]([\s\S]+)', data)
                if not answer_section:
                    messagebox.showerror("错误", "未找到答案部分")
                    exit()
                
                answers = re.findall(r'([A-E]+)\s*', answer_section.group(1))
                questions_data = re.split(r'【答案】', data)[0]
                
                # 分割题目
                questions = list(filter(None, re.split(r'(?:^|\n\s*)\d+?[\.\。]', questions_data)))
                if len(questions) != len(answers):
                    messagebox.showerror("错误", "题目数量与答案数量不匹配")
                    exit()
                
                for idx, q in enumerate(questions):
                    lines = re.split(r'\n', q, 1)
                    title = lines[0] if len(lines) > 0 else ''
                    content = lines[1] if len(lines) > 1 else ''
                    
                    # 提取选项
                    options = re.findall(r'[A-E][\.。]?(.+?)(?=\s+[A-E]|\s*$)', content, re.DOTALL)
                    
                    result.append({
                        "id": f"{time.strftime('%Y%m%d%H%M')}{random.randint(0,1000000)}",
                        "title": title,
                        "option": [o.strip() for o in options if o.strip()],
                        "answer": answers[idx],
                        "analysis": ""  # 该模式无法获取解析
                    })
            
            # 确保题库目录存在
            if not os.path.exists(题库目录):
                os.makedirs(题库目录)
             # 弹出目录选择对话框
            目标节点 = self.选择保存目录()
            if not 目标节点:
                return
                
            # 获取保存路径
            目标路径 = self.题库树.item(目标节点, "values")[1]
            if not os.path.isdir(目标路径):
                目标路径 = os.path.dirname(目标路径)
            # 生成文件名
            基础名称 = os.path.splitext(os.path.basename(file_path))[0]
            保存路径 = os.path.join(目标路径, f"{基础名称}.json")
            序号 = 1
            while os.path.exists(保存路径):
                保存路径 = os.path.join(题库目录, f"{基础名称}_{序号}.json")
                序号 += 1
            
            # 保存题库文件
            with open(保存路径, 'w', encoding='utf-8') as f:
                json.dump(result, f, ensure_ascii=False, indent=2)
            
            # 刷新对应目录
            self.刷新目录节点(目标节点)
            messagebox.showinfo("成功", "题库导入成功!")
            
        except Exception as e:
            messagebox.showerror("导入失败", str(e))
    
    def 选择保存目录(self):
        """弹出目录选择对话框"""
        当前选中 = self.题库树.selection()
        if 当前选中:
            当前节点 = 当前选中[0]
            if self.题库树.item(当前节点, "values")[0] == "dir":
                return 当前节点
        return self.题库根路径
    
    def 刷新目录节点(self, node):
        """刷新指定目录节点"""
        # 收起再展开以触发刷新
        self.题库树.item(node, open=False)
        self.题库树.item(node, open=True)
        
        # 强制更新子项
        path = self.题库树.item(node, "values")[1]
        for child in self.题库树.get_children(node):
            self.题库树.delete(child)
        self.加载子题库(path, parent=node)                

if __name__ == "__main__":
    窗口 = tk.Tk()
    窗口.geometry("1100x600")
    应用 = 做题软件(窗口)
    窗口.mainloop()

免费评分

参与人数 16吾爱币 +13 热心值 +14 收起 理由
涛哥依旧 + 1 + 1 谢谢@Thanks!
qq82707199 + 1 + 1 谢谢@Thanks!
Ghost_invisible + 1 谢谢@Thanks!
俺是新会员 + 1 + 1 谢谢@Thanks!
ICSMY + 1 谢谢@Thanks!
pojiez5 + 1 + 1 谢谢@Thanks!
ang123 + 1 谢谢@Thanks!
gengkaixuan + 1 + 1 用心讨论,共获提升!
0jiao0 + 1 + 1 谢谢@Thanks!
Rogers5 + 1 + 1 实用,巴适得很
chao0927 + 1 用心讨论,共获提升!
zhengzhenhui945 + 1 + 1 用心讨论,共获提升!
風信子 + 1 + 1 谢谢@Thanks!
cux666 + 1 谢谢@Thanks!
shirker + 1 + 1 谢谢@Thanks!
Wcneg + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

来自 #
 楼主| dzxsl 发表于 2025-4-24 16:23 |楼主
2025年4月24日跟新
---------------------------------------------------------------------------------------------------------
2.1.2版增加题目添加图片功能,欢迎大家来测;编译好的已放到原分享链接中,后续会放入代码

推荐
 楼主| dzxsl 发表于 2025-4-29 08:12 |楼主
TheRain16 发表于 2025-4-28 10:22
感谢楼主的分享!!!  下载尝试之后,真的很实用,看到有人留言增加图片的缩放功能,也很支持可以优化一下 ...

后续版本考虑加上
沙发
cux666 发表于 2025-4-10 23:20
3#
tinglie 发表于 2025-4-10 23:38
建议使用Anki
4#
luyiwanglei 发表于 2025-4-11 08:08
感谢楼主,什么时候能练到这种实力
5#
懒人的传说 发表于 2025-4-11 15:56
厉害,借鉴学习下
6#
wuai5858 发表于 2025-4-12 08:57
这个很好很实用,感谢楼主
7#
ElliotAlderson 发表于 2025-4-12 09:52
厉害楼楼,感谢
8#
ly_16 发表于 2025-4-14 00:49
谢谢分享!问添加解析说明,要在什么时候才会显示出来?
9#
enjoymessi 发表于 2025-4-14 16:03
请问支持填空题吗?
10#
 楼主| dzxsl 发表于 2025-4-14 20:06 |楼主
enjoymessi 发表于 2025-4-14 16:03
请问支持填空题吗?

暂不支持
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-7-21 15:17

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表