From f3d5eb33883f10f3b8274e5d1ae3363180686c39 Mon Sep 17 00:00:00 2001 From: unknown <1404612008@qq.com> Date: Sat, 11 Oct 2025 23:16:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9D=A8=E6=8C=AF=E5=AE=87=E7=9A=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=8F=90=E4=BA=A4=20-=202023226010113=20?= =?UTF-8?q?=E8=BD=AF=E4=BB=B62301=E7=8F=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/README.md | 136 +++++ doc/项目总结.md | 118 ++++ requirements.txt | 16 + run.bat | 5 + run.sh | 4 + .../question_generator.cpython-313.pyc | Bin 0 -> 21244 bytes src/__pycache__/user_manager.cpython-313.pyc | Bin 0 -> 7332 bytes src/main.py | 36 ++ src/question_generator.py | 519 ++++++++++++++++++ src/test.py | 72 +++ src/ui/__init__.py | 1 + src/ui/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 156 bytes .../__pycache__/login_window.cpython-313.pyc | Bin 0 -> 11206 bytes .../password_setup_window.cpython-313.pyc | Bin 0 -> 6495 bytes .../register_window.cpython-313.pyc | Bin 0 -> 7872 bytes src/ui/exam_window.py | 220 ++++++++ src/ui/level_selection_window.py | 113 ++++ src/ui/login_window.py | 195 +++++++ src/ui/main_window.py | 102 ++++ src/ui/password_setup_window.py | 109 ++++ src/ui/register_window.py | 129 +++++ src/ui/result_window.py | 138 +++++ src/user_manager.py | 162 ++++++ 23 files changed, 2075 insertions(+) create mode 100644 doc/README.md create mode 100644 doc/项目总结.md create mode 100644 requirements.txt create mode 100644 run.bat create mode 100644 run.sh create mode 100644 src/__pycache__/question_generator.cpython-313.pyc create mode 100644 src/__pycache__/user_manager.cpython-313.pyc create mode 100644 src/main.py create mode 100644 src/question_generator.py create mode 100644 src/test.py create mode 100644 src/ui/__init__.py create mode 100644 src/ui/__pycache__/__init__.cpython-313.pyc create mode 100644 src/ui/__pycache__/login_window.cpython-313.pyc create mode 100644 src/ui/__pycache__/password_setup_window.cpython-313.pyc create mode 100644 src/ui/__pycache__/register_window.cpython-313.pyc create mode 100644 src/ui/exam_window.py create mode 100644 src/ui/level_selection_window.py create mode 100644 src/ui/login_window.py create mode 100644 src/ui/main_window.py create mode 100644 src/ui/password_setup_window.py create mode 100644 src/ui/register_window.py create mode 100644 src/ui/result_window.py create mode 100644 src/user_manager.py diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..c764d9e --- /dev/null +++ b/doc/README.md @@ -0,0 +1,136 @@ +# 小初高数学学习软件 + +## 项目简介 + +这是一个面向小学、初中和高中学生的数学学习软件,提供图形化界面的数学练习功能。学生可以通过注册账号、选择学段、生成题目、答题练习来提高数学水平。 + +## 功能特性 + +### 1. 用户管理 +- **用户注册**:通过邮箱注册,系统发送验证码到邮箱 +- **密码设置**:密码要求6-10位,包含大小写字母和数字 +- **用户登录**:使用邮箱和密码登录 +- **密码修改**:登录状态下可修改密码 + +### 2. 学段选择 +- 支持小学、初中、高中三个学段 +- 每个学段对应不同难度的数学题目 + +### 3. 题目生成 +- **小学题目**:基础四则运算、分数、小数、几何、应用题 +- **初中题目**:代数、几何、函数、方程、统计 +- **高中题目**:三角函数、微积分、复数、数列、概率 +- 自动避免重复题目 + +### 4. 答题系统 +- 选择题形式,每题4个选项 +- 支持上一题/下一题导航 +- 实时显示答题进度 +- 自动评分和结果统计 + +### 5. 结果展示 +- 显示正确题数、总题数、正确率 +- 根据成绩给出评价 +- 支持继续做题或返回主菜单 + +## 技术架构 + +### 开发环境 +- **编程语言**:Python 3.7+ +- **GUI框架**:Tkinter +- **数据存储**:JSON文件(不使用数据库) + +### 项目结构 +``` +结对编程/ +├── src/ # 源代码目录 +│ ├── main.py # 主程序入口 +│ ├── user_manager.py # 用户管理模块 +│ ├── question_generator.py # 题目生成器 +│ └── ui/ # 用户界面模块 +│ ├── __init__.py +│ ├── login_window.py # 登录窗口 +│ ├── register_window.py # 注册窗口 +│ ├── password_setup_window.py # 密码设置窗口 +│ ├── main_window.py # 主窗口 +│ ├── level_selection_window.py # 学段选择窗口 +│ ├── exam_window.py # 答题窗口 +│ └── result_window.py # 结果窗口 +├── doc/ # 文档目录 +│ └── README.md # 项目说明文档 +└── users.json # 用户数据文件(运行时生成) +``` + +## 安装和运行 + +### 环境要求 +- Python 3.7 或更高版本 +- 无需额外安装第三方库(使用Python标准库) + +### 运行步骤 +1. 确保已安装Python 3.7+ +2. 进入项目目录 +3. 运行主程序: + ```bash + python src/main.py + ``` + +### 使用说明 +1. **注册账号**:点击"注册"按钮,输入邮箱获取验证码 +2. **设置密码**:验证码验证成功后设置密码 +3. **登录系统**:使用邮箱和密码登录 +4. **选择学段**:选择小学、初中或高中 +5. **设置题量**:输入要生成的题目数量(建议5-20题) +6. **开始答题**:依次回答每道题目 +7. **查看结果**:完成答题后查看成绩和评价 + +## 题目类型说明 + +### 小学题目 +- 基础四则运算(加减乘除) +- 分数运算 +- 小数运算 +- 基础几何(面积计算) +- 简单应用题 + +### 初中题目 +- 代数方程求解 +- 勾股定理应用 +- 一次函数 +- 一元一次方程 +- 基础统计(中位数) + +### 高中题目 +- 三角函数值 +- 导数计算 +- 复数模长 +- 等差数列 +- 概率计算 + +## 数据存储 + +- 用户数据存储在`users.json`文件中 +- 验证码临时存储在内存中,5分钟后自动过期 +- 不使用数据库,符合项目要求 + +## 注意事项 + +1. **邮箱验证码**:当前版本为模拟发送,实际项目中需要集成邮件服务 +2. **题目难度**:题目难度根据学段自动调整 +3. **数据安全**:密码以明文存储,实际项目中应加密存储 +4. **题目重复**:系统会自动避免同一张试卷中出现重复题目 + +## 开发团队 + +- 结对编程项目 +- 使用大模型辅助开发,人工修改完善 + +## 版本信息 + +- 版本:1.0.0 +- 开发时间:2024年 +- 适用平台:Windows、macOS、Linux + +## 许可证 + +本项目仅供学习和教育使用。 diff --git a/doc/项目总结.md b/doc/项目总结.md new file mode 100644 index 0000000..3ceb0ae --- /dev/null +++ b/doc/项目总结.md @@ -0,0 +1,118 @@ +# 结对编程项目总结 + +## 项目概述 + +本项目是一个面向小学、初中和高中学生的数学学习软件,采用Python + Tkinter技术栈开发,实现了完整的用户注册、登录、答题、评分等功能。 + +## 技术选型 + +### 选择Python + Tkinter的原因 +1. **开发效率高**:Python语法简洁,Tkinter是内置GUI库,无需额外安装 +2. **跨平台兼容**:支持Windows、macOS、Linux等主流操作系统 +3. **功能完整**:能够满足所有项目需求,包括图形界面、数据处理等 +4. **学习成本低**:团队成员对Python较为熟悉,降低开发难度 + +## 项目架构 + +### 模块化设计 +- **用户管理模块** (`user_manager.py`):处理注册、登录、密码管理 +- **题目生成模块** (`question_generator.py`):生成各学段数学题目 +- **UI界面模块** (`ui/`):包含所有用户界面窗口 +- **主程序** (`main.py`):程序入口点 + +### 数据存储 +- 使用JSON文件存储用户数据,符合"不使用数据库"的要求 +- 验证码临时存储在内存中,5分钟后自动过期 + +## 功能实现 + +### 1. 用户注册系统 +- ✅ 邮箱格式验证 +- ✅ 验证码生成和验证(模拟发送) +- ✅ 密码强度验证(6-10位,包含大小写字母和数字) +- ✅ 用户数据持久化存储 + +### 2. 用户登录系统 +- ✅ 邮箱密码登录 +- ✅ 密码修改功能 +- ✅ 登录状态管理 + +### 3. 学段选择系统 +- ✅ 小学、初中、高中三个学段 +- ✅ 题目数量自定义(1-50题) +- ✅ 界面友好,操作简单 + +### 4. 题目生成系统 +- ✅ 小学题目:基础运算、分数、小数、几何、应用题 +- ✅ 初中题目:代数、几何、函数、方程、统计 +- ✅ 高中题目:三角函数、微积分、复数、数列、概率 +- ✅ 自动避免重复题目 + +### 5. 答题系统 +- ✅ 选择题形式,每题4个选项 +- ✅ 支持题目导航(上一题/下一题) +- ✅ 实时进度显示 +- ✅ 答案自动保存 + +### 6. 评分系统 +- ✅ 自动计算正确率 +- ✅ 成绩评价(优秀、良好、及格等) +- ✅ 结果展示界面 + +## 代码质量 + +### 优点 +1. **模块化设计**:代码结构清晰,各模块职责明确 +2. **错误处理**:完善的异常处理和用户提示 +3. **用户体验**:界面友好,操作流程顺畅 +4. **代码规范**:遵循PEP8编码规范,注释完整 +5. **功能完整**:满足所有项目需求 + +### 改进空间 +1. **安全性**:密码应加密存储,当前为明文存储 +2. **邮件服务**:验证码发送需要集成真实邮件服务 +3. **题目扩展**:可以增加更多题目类型和难度级别 +4. **数据备份**:可以增加数据备份和恢复功能 + +## 测试验证 + +### 功能测试 +- ✅ 用户注册流程测试 +- ✅ 用户登录测试 +- ✅ 题目生成测试 +- ✅ 答题流程测试 +- ✅ 评分系统测试 + +### 测试结果 +所有核心功能均正常工作,程序运行稳定,无语法错误。 + +## 项目亮点 + +1. **完整的用户系统**:从注册到登录的完整流程 +2. **智能题目生成**:根据学段自动生成合适难度的题目 +3. **友好的用户界面**:简洁美观的GUI设计 +4. **良好的代码结构**:模块化设计,易于维护和扩展 +5. **跨平台兼容**:支持多种操作系统 + +## 部署说明 + +### 环境要求 +- Python 3.7+ +- 无需额外依赖包 + +### 运行方式 +1. 直接运行:`python src/main.py` +2. Windows批处理:双击`run.bat` +3. Linux/Mac脚本:`./run.sh` + +## 总结 + +本项目成功实现了所有需求功能,代码质量良好,用户体验友好。通过模块化设计和完善的错误处理,确保了程序的稳定性和可维护性。项目符合结对编程的要求,展现了良好的团队协作和代码规范。 + +## 后续优化建议 + +1. **安全性提升**:实现密码加密存储 +2. **功能扩展**:增加题目收藏、错题本等功能 +3. **性能优化**:优化题目生成算法 +4. **界面美化**:使用更现代的UI框架 +5. **数据分析**:增加学习进度统计功能 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e77d8b3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,16 @@ +# 小初高数学学习软件依赖包 +# 本项目使用Python标准库,无需额外安装第三方包 + +# Python版本要求 +# Python >= 3.7 + +# 使用的标准库模块: +# - tkinter (GUI界面) +# - json (数据存储) +# - os (文件操作) +# - sys (系统操作) +# - re (正则表达式) +# - random (随机数生成) +# - string (字符串操作) +# - math (数学计算) +# - datetime (时间处理) diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..ec6b661 --- /dev/null +++ b/run.bat @@ -0,0 +1,5 @@ +@echo off +echo 启动小初高数学学习软件... +echo. +python src/main.py +pause diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..42f7028 --- /dev/null +++ b/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "启动小初高数学学习软件..." +echo "" +python3 src/main.py diff --git a/src/__pycache__/question_generator.cpython-313.pyc b/src/__pycache__/question_generator.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..72e0629b420042a9d638776bd207cef11ed4a807 GIT binary patch literal 21244 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 literal 0 HcmV?d00001 diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..2305094 --- /dev/null +++ b/src/main.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +小初高数学学习软件 +主程序入口 +""" + +import tkinter as tk +from tkinter import messagebox +import sys +import os + +# 添加当前目录到Python路径 +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from ui.login_window import LoginWindow + +def main(): + """主函数""" + try: + # 创建主窗口 + root = tk.Tk() + root.withdraw() # 隐藏主窗口 + + # 创建登录窗口 + login_window = LoginWindow() + + # 运行应用 + root.mainloop() + + except Exception as e: + messagebox.showerror("错误", f"程序启动失败: {str(e)}") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/src/question_generator.py b/src/question_generator.py new file mode 100644 index 0000000..bd1968e --- /dev/null +++ b/src/question_generator.py @@ -0,0 +1,519 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +数学题目生成器 +生成小学、初中、高中的数学选择题 +""" + +import random +import math + +class QuestionGenerator: + """数学题目生成器""" + + def __init__(self): + self.generated_questions = set() # 用于避免重复题目 + + def generate_questions(self, level, count): + """生成指定数量和难度的题目""" + questions = [] + self.generated_questions.clear() + + for _ in range(count): + question = self._generate_single_question(level) + # 确保题目不重复 + while str(question) in self.generated_questions: + question = self._generate_single_question(level) + + self.generated_questions.add(str(question)) + questions.append(question) + + return questions + + def _generate_single_question(self, level): + """生成单个题目""" + if level == "小学": + return self._generate_primary_question() + elif level == "初中": + return self._generate_middle_question() + elif level == "高中": + return self._generate_high_question() + else: + raise ValueError("不支持的学段") + + def _generate_primary_question(self): + """生成小学题目""" + question_types = [ + self._basic_arithmetic, + self._fraction_question, + self._decimal_question, + self._geometry_question, + self._word_problem + ] + + return random.choice(question_types)() + + def _generate_middle_question(self): + """生成初中题目""" + question_types = [ + self._algebra_question, + self._geometry_advanced, + self._function_question, + self._equation_question, + self._statistics_question + ] + + return random.choice(question_types)() + + def _generate_high_question(self): + """生成高中题目""" + question_types = [ + self._trigonometry_question, + self._calculus_question, + self._complex_number_question, + self._sequence_question, + self._probability_question + ] + + return random.choice(question_types)() + + def _basic_arithmetic(self): + """基础四则运算""" + operations = ['+', '-', '*', '/'] + op = random.choice(operations) + + if op == '+': + a, b = random.randint(1, 100), random.randint(1, 100) + answer = a + b + question = f"{a} + {b} = ?" + elif op == '-': + a, b = random.randint(50, 100), random.randint(1, 50) + answer = a - b + question = f"{a} - {b} = ?" + elif op == '*': + a, b = random.randint(1, 12), random.randint(1, 12) + answer = a * b + question = f"{a} × {b} = ?" + else: # division + b = random.randint(2, 12) + answer = random.randint(1, 12) + a = b * answer + question = f"{a} ÷ {b} = ?" + + # 生成错误选项 + options = [answer] + while len(options) < 4: + wrong = answer + random.randint(-10, 10) + if wrong != answer and wrong > 0 and wrong not in options: + options.append(wrong) + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '小学' + } + + def _fraction_question(self): + """分数题目""" + # 简单的分数加法 + a1, b1 = random.randint(1, 5), random.randint(2, 8) + a2, b2 = random.randint(1, 5), random.randint(2, 8) + + # 确保分母相同 + b1 = b2 = random.randint(2, 8) + + question = f"{a1}/{b1} + {a2}/{b2} = ?" + + # 计算答案 + numerator = a1 + a2 + denominator = b1 + + # 简化分数 + gcd_val = math.gcd(numerator, denominator) + answer_num = numerator // gcd_val + answer_den = denominator // gcd_val + + if answer_den == 1: + answer = answer_num + else: + answer = f"{answer_num}/{answer_den}" + + # 生成选项 + options = [answer] + while len(options) < 4: + if isinstance(answer, int): + wrong = answer + random.randint(-2, 2) + if wrong != answer and wrong > 0: + options.append(wrong) + else: + wrong_num = answer_num + random.randint(-2, 2) + wrong_den = answer_den + random.randint(-1, 1) + if wrong_den <= 0: + wrong_den = answer_den + if wrong_num > 0: + options.append(f"{wrong_num}/{wrong_den}") + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '小学' + } + + def _decimal_question(self): + """小数题目""" + a = round(random.uniform(1, 10), 1) + b = round(random.uniform(1, 10), 1) + + question = f"{a} + {b} = ?" + answer = round(a + b, 1) + + options = [answer] + while len(options) < 4: + wrong = round(answer + random.uniform(-2, 2), 1) + if wrong != answer and wrong > 0 and wrong not in options: + options.append(wrong) + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '小学' + } + + def _geometry_question(self): + """几何题目""" + shapes = ['正方形', '长方形', '圆形', '三角形'] + shape = random.choice(shapes) + + if shape == '正方形': + side = random.randint(2, 10) + question = f"边长为{side}的正方形的面积是?" + answer = side * side + elif shape == '长方形': + length = random.randint(3, 10) + width = random.randint(2, 8) + question = f"长为{length},宽为{width}的长方形的面积是?" + answer = length * width + elif shape == '圆形': + radius = random.randint(2, 8) + question = f"半径为{radius}的圆的面积是?(π取3.14)" + answer = round(3.14 * radius * radius, 2) + else: # 三角形 + base = random.randint(3, 10) + height = random.randint(2, 8) + question = f"底为{base},高为{height}的三角形的面积是?" + answer = base * height // 2 + + options = [answer] + while len(options) < 4: + wrong = answer + random.randint(-5, 5) + if wrong != answer and wrong > 0 and wrong not in options: + options.append(wrong) + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '小学' + } + + def _word_problem(self): + """应用题""" + problems = [ + ("小明有{}个苹果,吃了{}个,还剩多少个?", lambda x, y: x - y), + ("小红有{}元钱,买书花了{}元,还剩多少元?", lambda x, y: x - y), + ("班级有{}个学生,又来了{}个新同学,现在有多少个学生?", lambda x, y: x + y), + ("一盒铅笔有{}支,{}盒铅笔一共有多少支?", lambda x, y: x * y) + ] + + problem, func = random.choice(problems) + a = random.randint(1, 20) + b = random.randint(1, 10) + + question = problem.format(a, b) + answer = func(a, b) + + options = [answer] + while len(options) < 4: + wrong = answer + random.randint(-3, 3) + if wrong != answer and wrong > 0 and wrong not in options: + options.append(wrong) + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '小学' + } + + def _algebra_question(self): + """代数题目""" + x = random.randint(1, 10) + a = random.randint(2, 10) + b = random.randint(1, 20) + + question = f"解方程:{a}x + {b} = {a*x + b}" + answer = x + + options = [answer] + while len(options) < 4: + wrong = answer + random.randint(-3, 3) + if wrong != answer and wrong > 0 and wrong not in options: + options.append(wrong) + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '初中' + } + + def _geometry_advanced(self): + """高级几何题目""" + # 勾股定理 + a = random.randint(3, 8) + b = random.randint(3, 8) + c = round(math.sqrt(a*a + b*b), 2) + + question = f"直角三角形的两条直角边分别为{a}和{b},斜边长为?" + answer = c + + options = [answer] + while len(options) < 4: + wrong = round(c + random.uniform(-2, 2), 2) + if wrong != answer and wrong > 0 and wrong not in options: + options.append(wrong) + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '初中' + } + + def _function_question(self): + """函数题目""" + a = random.randint(1, 5) + b = random.randint(1, 10) + x = random.randint(1, 10) + + question = f"函数f(x) = {a}x + {b},求f({x})的值" + answer = a * x + b + + options = [answer] + while len(options) < 4: + wrong = answer + random.randint(-5, 5) + if wrong != answer and wrong not in options: + options.append(wrong) + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '初中' + } + + def _equation_question(self): + """方程题目""" + x = random.randint(1, 10) + a = random.randint(2, 5) + b = random.randint(1, 10) + + question = f"解方程:{a}x - {b} = {a*x - b}" + answer = x + + options = [answer] + while len(options) < 4: + wrong = answer + random.randint(-3, 3) + if wrong != answer and wrong not in options: + options.append(wrong) + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '初中' + } + + def _statistics_question(self): + """统计题目""" + numbers = [random.randint(1, 20) for _ in range(5)] + question = f"数据{numbers}的中位数是?" + + sorted_nums = sorted(numbers) + answer = sorted_nums[2] # 中位数 + + options = [answer] + while len(options) < 4: + wrong = answer + random.randint(-3, 3) + if wrong != answer and wrong > 0 and wrong not in options: + options.append(wrong) + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '初中' + } + + def _trigonometry_question(self): + """三角函数题目""" + angle = random.choice([30, 45, 60]) + question = f"sin({angle}°) = ?" + + if angle == 30: + answer = "1/2" + elif angle == 45: + answer = "√2/2" + else: # 60 + answer = "√3/2" + + options = [answer, "1/2", "√2/2", "√3/2"] + options = list(set(options)) # 去重 + + while len(options) < 4: + options.append(f"{random.randint(1,3)}/{random.randint(2,4)}") + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '高中' + } + + def _calculus_question(self): + """微积分题目""" + # 简单的导数 + a = random.randint(1, 5) + b = random.randint(1, 10) + + question = f"函数f(x) = {a}x² + {b}x的导数是?" + answer = f"{2*a}x + {b}" + + options = [answer] + while len(options) < 4: + wrong_a = random.randint(1, 5) + wrong_b = random.randint(1, 10) + if f"{wrong_a}x + {wrong_b}" != answer: + options.append(f"{wrong_a}x + {wrong_b}") + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '高中' + } + + def _complex_number_question(self): + """复数题目""" + a = random.randint(1, 5) + b = random.randint(1, 5) + + question = f"复数{a} + {b}i的模长是?" + answer = round(math.sqrt(a*a + b*b), 2) + + options = [answer] + while len(options) < 4: + wrong = round(answer + random.uniform(-2, 2), 2) + if wrong != answer and wrong > 0 and wrong not in options: + options.append(wrong) + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '高中' + } + + def _sequence_question(self): + """数列题目""" + a1 = random.randint(1, 10) + d = random.randint(1, 5) + n = random.randint(3, 8) + + question = f"等差数列首项为{a1},公差为{d},第{n}项是?" + answer = a1 + (n-1) * d + + options = [answer] + while len(options) < 4: + wrong = answer + random.randint(-5, 5) + if wrong != answer and wrong not in options: + options.append(wrong) + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '高中' + } + + def _probability_question(self): + """概率题目""" + # 简单的概率计算 + total = random.randint(10, 20) + favorable = random.randint(1, total-1) + + question = f"袋子里有{total}个球,其中{favorable}个是红球,随机取出一个球是红球的概率是?" + answer = f"{favorable}/{total}" + + options = [answer] + while len(options) < 4: + wrong_total = random.randint(5, 25) + wrong_favorable = random.randint(1, wrong_total-1) + wrong_answer = f"{wrong_favorable}/{wrong_total}" + if wrong_answer != answer and wrong_answer not in options: + options.append(wrong_answer) + + random.shuffle(options) + correct_index = options.index(answer) + + return { + 'question': question, + 'options': options, + 'correct_answer': correct_index, + 'level': '高中' + } diff --git a/src/test.py b/src/test.py new file mode 100644 index 0000000..dadf17f --- /dev/null +++ b/src/test.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +测试脚本 +用于验证各个模块的功能 +""" + +import sys +import os + +# 添加当前目录到Python路径 +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from user_manager import UserManager +from question_generator import QuestionGenerator + +def test_user_manager(): + """测试用户管理功能""" + print("测试用户管理功能...") + + user_manager = UserManager() + + # 测试邮箱验证 + test_email = "test@example.com" + print(f"测试邮箱格式验证: {test_email} -> {user_manager.is_valid_email(test_email)}") + + # 测试密码验证 + test_passwords = ["Abc123", "abc123", "ABC123", "Abcdef", "123456", "Abc123456789"] + for pwd in test_passwords: + print(f"测试密码格式验证: {pwd} -> {user_manager.is_valid_password(pwd)}") + + print("用户管理功能测试完成\n") + +def test_question_generator(): + """测试题目生成功能""" + print("测试题目生成功能...") + + generator = QuestionGenerator() + + # 测试各学段题目生成 + levels = ["小学", "初中", "高中"] + for level in levels: + print(f"\n生成{level}题目:") + questions = generator.generate_questions(level, 3) + for i, q in enumerate(questions, 1): + print(f" 题目{i}: {q['question']}") + print(f" 选项: {q['options']}") + print(f" 正确答案: {q['correct_answer']}") + + print("\n题目生成功能测试完成") + +def main(): + """主测试函数""" + print("=" * 50) + print("小初高数学学习软件 - 功能测试") + print("=" * 50) + + try: + test_user_manager() + test_question_generator() + + print("=" * 50) + print("所有测试完成!") + print("=" * 50) + + except Exception as e: + print(f"测试过程中出现错误: {e}") + import traceback + traceback.print_exc() + +if __name__ == "__main__": + main() diff --git a/src/ui/__init__.py b/src/ui/__init__.py new file mode 100644 index 0000000..22f73b9 --- /dev/null +++ b/src/ui/__init__.py @@ -0,0 +1 @@ +# UI模块初始化文件 diff --git a/src/ui/__pycache__/__init__.cpython-313.pyc b/src/ui/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f197a6d20dbef442f85a58233e8f5d3e0c72972c GIT binary patch literal 156 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 len(self.questions): + return + + question = self.questions[self.current_question] + + # 更新进度 + self.progress_label.config(text=f"第 {self.current_question + 1} 题 / 共 {len(self.questions)} 题") + + # 显示题目 + self.question_label.config(text=question['question']) + + # 显示选项 + for i, option in enumerate(question['options']): + self.option_buttons[i].config(text=f"{chr(65+i)}. {option}") + + # 清除选择 + for var in self.option_vars: + var.set("") + + # 如果之前已经选择过,恢复选择 + if self.current_question < len(self.user_answers): + if self.user_answers[self.current_question] is not None: + self.option_vars[self.user_answers[self.current_question]].set(str(self.user_answers[self.current_question])) + + # 更新按钮状态 + self.update_button_states() + + def on_option_selected(self): + """选项被选择时的处理""" + self.update_button_states() + + def update_button_states(self): + """更新按钮状态""" + # 检查是否有选择 + has_selection = any(var.get() for var in self.option_vars) + + # 更新提交按钮状态 + self.submit_btn.config(state='normal' if has_selection else 'disabled') + + # 更新上一题按钮状态 + self.prev_btn.config(state='normal' if self.current_question > 0 else 'disabled') + + # 更新下一题按钮状态 + self.next_btn.config(state='normal' if self.current_question < len(self.questions) - 1 else 'disabled') + + # 更新完成按钮状态 + all_answered = all(answer is not None for answer in self.user_answers) + self.finish_btn.config(state='normal' if all_answered else 'disabled') + + def submit_answer(self): + """提交当前题目的答案""" + # 获取选择的答案 + selected_option = None + for i, var in enumerate(self.option_vars): + if var.get(): + selected_option = i + break + + if selected_option is None: + messagebox.showerror("错误", "请选择一个答案") + return + + # 保存答案 + while len(self.user_answers) <= self.current_question: + self.user_answers.append(None) + + self.user_answers[self.current_question] = selected_option + + # 检查答案是否正确 + question = self.questions[self.current_question] + if selected_option == question['correct_answer']: + self.score += 1 + + # 自动跳转到下一题 + if self.current_question < len(self.questions) - 1: + self.next_question() + else: + # 最后一题,显示完成提示 + messagebox.showinfo("完成", "所有题目已完成!") + self.update_button_states() + + def previous_question(self): + """上一题""" + if self.current_question > 0: + self.current_question -= 1 + self.show_question() + + def next_question(self): + """下一题""" + if self.current_question < len(self.questions) - 1: + self.current_question += 1 + self.show_question() + + def finish_exam(self): + """完成答题""" + # 计算最终分数 + correct_count = 0 + for i, answer in enumerate(self.user_answers): + if answer is not None and answer == self.questions[i]['correct_answer']: + correct_count += 1 + + percentage = (correct_count / len(self.questions)) * 100 + + self.window.destroy() + # 打开结果窗口 + result_window = ResultWindow(self.email, self.level, correct_count, + len(self.questions), percentage) diff --git a/src/ui/level_selection_window.py b/src/ui/level_selection_window.py new file mode 100644 index 0000000..b7e949d --- /dev/null +++ b/src/ui/level_selection_window.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +学段选择窗口 +处理题目数量输入和开始答题 +""" + +import tkinter as tk +from tkinter import messagebox, ttk +import sys +import os + +# 添加父目录到路径 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from ui.exam_window import ExamWindow + +class LevelSelectionWindow: + """学段选择窗口类""" + + def __init__(self, email, level): + self.email = email + self.level = level + self.setup_ui() + + def setup_ui(self): + """设置UI界面""" + self.window = tk.Tk() + self.window.title(f"数学学习软件 - {self.level}") + self.window.geometry("400x300") + self.window.resizable(False, False) + + # 居中显示 + self.center_window() + + # 创建主框架 + main_frame = ttk.Frame(self.window, padding="30") + main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + + # 标题 + title_label = ttk.Label(main_frame, text=f"{self.level}数学练习", + font=("Arial", 16, "bold")) + title_label.grid(row=0, column=0, columnspan=2, pady=(0, 30)) + + # 题目数量输入 + ttk.Label(main_frame, text="请输入题目数量:", + font=("Arial", 12)).grid(row=1, column=0, sticky=tk.W, pady=10) + + self.count_var = tk.StringVar() + self.count_entry = ttk.Entry(main_frame, textvariable=self.count_var, + width=20, font=("Arial", 12)) + self.count_entry.grid(row=1, column=1, pady=10, padx=(10, 0)) + + # 提示信息 + info_text = "建议题目数量:5-20题" + info_label = ttk.Label(main_frame, text=info_text, + font=("Arial", 9), foreground="gray") + info_label.grid(row=2, column=0, columnspan=2, pady=(0, 20)) + + # 按钮框架 + button_frame = ttk.Frame(main_frame) + button_frame.grid(row=3, column=0, columnspan=2, pady=20) + + # 开始答题按钮 + start_btn = ttk.Button(button_frame, text="开始答题", + command=self.start_exam, style="Large.TButton") + start_btn.grid(row=0, column=0, padx=10) + + # 返回按钮 + back_btn = ttk.Button(button_frame, text="返回", + command=self.go_back) + back_btn.grid(row=0, column=1, padx=10) + + # 配置大按钮样式 + style = ttk.Style() + style.configure("Large.TButton", font=("Arial", 12, "bold")) + + # 绑定回车键 + self.window.bind('', lambda e: self.start_exam()) + + # 设置焦点 + self.count_entry.focus() + + def center_window(self): + """窗口居中显示""" + self.window.update_idletasks() + width = self.window.winfo_width() + height = self.window.winfo_height() + x = (self.window.winfo_screenwidth() // 2) - (width // 2) + y = (self.window.winfo_screenheight() // 2) - (height // 2) + self.window.geometry(f'{width}x{height}+{x}+{y}') + + def start_exam(self): + """开始答题""" + try: + count = int(self.count_var.get().strip()) + + if count < 1 or count > 50: + messagebox.showerror("错误", "题目数量应在1-50之间") + return + + self.window.destroy() + # 打开答题窗口 + exam_window = ExamWindow(self.email, self.level, count) + + except ValueError: + messagebox.showerror("错误", "请输入有效的数字") + + def go_back(self): + """返回主窗口""" + self.window.destroy() + from ui.main_window import MainWindow + main_window = MainWindow(self.email) diff --git a/src/ui/login_window.py b/src/ui/login_window.py new file mode 100644 index 0000000..f170874 --- /dev/null +++ b/src/ui/login_window.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +登录窗口 +处理用户注册和登录功能 +""" + +import tkinter as tk +from tkinter import messagebox, ttk +import sys +import os + +# 添加父目录到路径 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from user_manager import UserManager +from ui.register_window import RegisterWindow +from ui.main_window import MainWindow + +class LoginWindow: + """登录窗口类""" + + def __init__(self): + self.user_manager = UserManager() + self.setup_ui() + + def setup_ui(self): + """设置UI界面""" + self.window = tk.Tk() + self.window.title("数学学习软件 - 登录") + self.window.geometry("400x300") + self.window.resizable(False, False) + + # 居中显示 + self.center_window() + + # 创建主框架 + main_frame = ttk.Frame(self.window, padding="20") + main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + + # 标题 + title_label = ttk.Label(main_frame, text="数学学习软件", font=("Arial", 16, "bold")) + title_label.grid(row=0, column=0, columnspan=2, pady=(0, 20)) + + # 邮箱输入 + ttk.Label(main_frame, text="邮箱:").grid(row=1, column=0, sticky=tk.W, pady=5) + self.email_var = tk.StringVar() + self.email_entry = ttk.Entry(main_frame, textvariable=self.email_var, width=30) + self.email_entry.grid(row=1, column=1, pady=5, padx=(10, 0)) + + # 密码输入 + ttk.Label(main_frame, text="密码:").grid(row=2, column=0, sticky=tk.W, pady=5) + self.password_var = tk.StringVar() + self.password_entry = ttk.Entry(main_frame, textvariable=self.password_var, + show="*", width=30) + self.password_entry.grid(row=2, column=1, pady=5, padx=(10, 0)) + + # 按钮框架 + button_frame = ttk.Frame(main_frame) + button_frame.grid(row=3, column=0, columnspan=2, pady=20) + + # 登录按钮 + login_btn = ttk.Button(button_frame, text="登录", command=self.login) + login_btn.grid(row=0, column=0, padx=5) + + # 注册按钮 + register_btn = ttk.Button(button_frame, text="注册", command=self.open_register) + register_btn.grid(row=0, column=1, padx=5) + + # 修改密码按钮 + change_pwd_btn = ttk.Button(button_frame, text="修改密码", command=self.open_change_password) + change_pwd_btn.grid(row=0, column=2, padx=5) + + # 绑定回车键 + self.window.bind('', lambda e: self.login()) + + # 设置焦点 + self.email_entry.focus() + + def center_window(self): + """窗口居中显示""" + self.window.update_idletasks() + width = self.window.winfo_width() + height = self.window.winfo_height() + x = (self.window.winfo_screenwidth() // 2) - (width // 2) + y = (self.window.winfo_screenheight() // 2) - (height // 2) + self.window.geometry(f'{width}x{height}+{x}+{y}') + + def login(self): + """处理登录""" + email = self.email_var.get().strip() + password = self.password_var.get().strip() + + if not email or not password: + messagebox.showerror("错误", "请输入邮箱和密码") + return + + success, message = self.user_manager.login(email, password) + + if success: + messagebox.showinfo("成功", message) + self.window.destroy() + # 打开主窗口 + main_window = MainWindow(email) + else: + messagebox.showerror("错误", message) + + def open_register(self): + """打开注册窗口""" + self.window.destroy() + register_window = RegisterWindow() + + def open_change_password(self): + """打开修改密码窗口""" + email = self.email_var.get().strip() + if not email: + messagebox.showerror("错误", "请先输入邮箱") + return + + if email not in self.user_manager.users: + messagebox.showerror("错误", "用户不存在") + return + + # 创建修改密码对话框 + self.change_password_dialog(email) + + def change_password_dialog(self, email): + """修改密码对话框""" + dialog = tk.Toplevel(self.window) + dialog.title("修改密码") + dialog.geometry("350x200") + dialog.resizable(False, False) + dialog.transient(self.window) + dialog.grab_set() + + # 居中显示 + dialog.update_idletasks() + x = (dialog.winfo_screenwidth() // 2) - (175) + y = (dialog.winfo_screenheight() // 2) - (100) + dialog.geometry(f'350x200+{x}+{y}') + + main_frame = ttk.Frame(dialog, padding="20") + main_frame.pack(fill=tk.BOTH, expand=True) + + # 原密码 + ttk.Label(main_frame, text="原密码:").grid(row=0, column=0, sticky=tk.W, pady=5) + old_password_var = tk.StringVar() + old_password_entry = ttk.Entry(main_frame, textvariable=old_password_var, + show="*", width=25) + old_password_entry.grid(row=0, column=1, pady=5, padx=(10, 0)) + + # 新密码 + ttk.Label(main_frame, text="新密码:").grid(row=1, column=0, sticky=tk.W, pady=5) + new_password_var = tk.StringVar() + new_password_entry = ttk.Entry(main_frame, textvariable=new_password_var, + show="*", width=25) + new_password_entry.grid(row=1, column=1, pady=5, padx=(10, 0)) + + # 确认新密码 + ttk.Label(main_frame, text="确认新密码:").grid(row=2, column=0, sticky=tk.W, pady=5) + confirm_password_var = tk.StringVar() + confirm_password_entry = ttk.Entry(main_frame, textvariable=confirm_password_var, + show="*", width=25) + confirm_password_entry.grid(row=2, column=1, pady=5, padx=(10, 0)) + + def change_password(): + old_password = old_password_var.get().strip() + new_password = new_password_var.get().strip() + confirm_password = confirm_password_var.get().strip() + + if not all([old_password, new_password, confirm_password]): + messagebox.showerror("错误", "请填写所有字段") + return + + if new_password != confirm_password: + messagebox.showerror("错误", "两次输入的新密码不一致") + return + + success, message = self.user_manager.change_password(email, old_password, new_password) + + if success: + messagebox.showinfo("成功", message) + dialog.destroy() + else: + messagebox.showerror("错误", message) + + # 按钮 + button_frame = ttk.Frame(main_frame) + button_frame.grid(row=3, column=0, columnspan=2, pady=20) + + ttk.Button(button_frame, text="确定", command=change_password).grid(row=0, column=0, padx=5) + ttk.Button(button_frame, text="取消", command=dialog.destroy).grid(row=0, column=1, padx=5) + + # 设置焦点 + old_password_entry.focus() diff --git a/src/ui/main_window.py b/src/ui/main_window.py new file mode 100644 index 0000000..554dc4a --- /dev/null +++ b/src/ui/main_window.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +主窗口 +显示学段选择界面 +""" + +import tkinter as tk +from tkinter import messagebox, ttk +import sys +import os + +# 添加父目录到路径 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from ui.level_selection_window import LevelSelectionWindow + +class MainWindow: + """主窗口类""" + + def __init__(self, email): + self.email = email + self.setup_ui() + + def setup_ui(self): + """设置UI界面""" + self.window = tk.Tk() + self.window.title("数学学习软件 - 主界面") + self.window.geometry("500x400") + self.window.resizable(False, False) + + # 居中显示 + self.center_window() + + # 创建主框架 + main_frame = ttk.Frame(self.window, padding="30") + main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + + # 欢迎信息 + welcome_label = ttk.Label(main_frame, text=f"欢迎,{self.email}", + font=("Arial", 14, "bold")) + welcome_label.grid(row=0, column=0, columnspan=3, pady=(0, 30)) + + # 标题 + title_label = ttk.Label(main_frame, text="请选择学习阶段", + font=("Arial", 16, "bold")) + title_label.grid(row=1, column=0, columnspan=3, pady=(0, 30)) + + # 学段选择按钮 + button_frame = ttk.Frame(main_frame) + button_frame.grid(row=2, column=0, columnspan=3, pady=20) + + # 小学按钮 + primary_btn = ttk.Button(button_frame, text="小学", + command=lambda: self.select_level("小学"), + width=15, style="Large.TButton") + primary_btn.grid(row=0, column=0, padx=10, pady=10) + + # 初中按钮 + middle_btn = ttk.Button(button_frame, text="初中", + command=lambda: self.select_level("初中"), + width=15, style="Large.TButton") + middle_btn.grid(row=0, column=1, padx=10, pady=10) + + # 高中按钮 + high_btn = ttk.Button(button_frame, text="高中", + command=lambda: self.select_level("高中"), + width=15, style="Large.TButton") + high_btn.grid(row=0, column=2, padx=10, pady=10) + + # 底部按钮框架 + bottom_frame = ttk.Frame(main_frame) + bottom_frame.grid(row=3, column=0, columnspan=3, pady=30) + + # 退出按钮 + exit_btn = ttk.Button(bottom_frame, text="退出", command=self.exit_app) + exit_btn.grid(row=0, column=0, padx=10) + + # 配置大按钮样式 + style = ttk.Style() + style.configure("Large.TButton", font=("Arial", 12, "bold")) + + def center_window(self): + """窗口居中显示""" + self.window.update_idletasks() + width = self.window.winfo_width() + height = self.window.winfo_height() + x = (self.window.winfo_screenwidth() // 2) - (width // 2) + y = (self.window.winfo_screenheight() // 2) - (height // 2) + self.window.geometry(f'{width}x{height}+{x}+{y}') + + def select_level(self, level): + """选择学段""" + self.window.destroy() + # 打开学段选择窗口 + level_selection_window = LevelSelectionWindow(self.email, level) + + def exit_app(self): + """退出应用""" + if messagebox.askyesno("确认", "确定要退出吗?"): + self.window.destroy() + sys.exit(0) diff --git a/src/ui/password_setup_window.py b/src/ui/password_setup_window.py new file mode 100644 index 0000000..5707a01 --- /dev/null +++ b/src/ui/password_setup_window.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +密码设置窗口 +处理用户密码设置功能 +""" + +import tkinter as tk +from tkinter import messagebox, ttk +import sys +import os + +# 添加父目录到路径 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from user_manager import UserManager +from ui.login_window import LoginWindow + +class PasswordSetupWindow: + """密码设置窗口类""" + + def __init__(self, email): + self.email = email + self.user_manager = UserManager() + self.setup_ui() + + def setup_ui(self): + """设置UI界面""" + self.window = tk.Tk() + self.window.title("数学学习软件 - 设置密码") + self.window.geometry("400x250") + self.window.resizable(False, False) + + # 居中显示 + self.center_window() + + # 创建主框架 + main_frame = ttk.Frame(self.window, padding="20") + main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + + # 标题 + title_label = ttk.Label(main_frame, text="设置密码", font=("Arial", 16, "bold")) + title_label.grid(row=0, column=0, columnspan=2, pady=(0, 20)) + + # 密码要求说明 + info_text = "密码要求:6-10位,包含大小写字母和数字" + info_label = ttk.Label(main_frame, text=info_text, font=("Arial", 9), foreground="gray") + info_label.grid(row=1, column=0, columnspan=2, pady=(0, 10)) + + # 密码输入 + ttk.Label(main_frame, text="密码:").grid(row=2, column=0, sticky=tk.W, pady=5) + self.password_var = tk.StringVar() + self.password_entry = ttk.Entry(main_frame, textvariable=self.password_var, + show="*", width=30) + self.password_entry.grid(row=2, column=1, pady=5, padx=(10, 0)) + + # 确认密码输入 + ttk.Label(main_frame, text="确认密码:").grid(row=3, column=0, sticky=tk.W, pady=5) + self.confirm_password_var = tk.StringVar() + self.confirm_password_entry = ttk.Entry(main_frame, textvariable=self.confirm_password_var, + show="*", width=30) + self.confirm_password_entry.grid(row=3, column=1, pady=5, padx=(10, 0)) + + # 按钮框架 + button_frame = ttk.Frame(main_frame) + button_frame.grid(row=4, column=0, columnspan=2, pady=20) + + # 设置密码按钮 + setup_btn = ttk.Button(button_frame, text="设置密码", command=self.setup_password) + setup_btn.grid(row=0, column=0, padx=5) + + # 绑定回车键 + self.window.bind('', lambda e: self.setup_password()) + + # 设置焦点 + self.password_entry.focus() + + def center_window(self): + """窗口居中显示""" + self.window.update_idletasks() + width = self.window.winfo_width() + height = self.window.winfo_height() + x = (self.window.winfo_screenwidth() // 2) - (width // 2) + y = (self.window.winfo_screenheight() // 2) - (height // 2) + self.window.geometry(f'{width}x{height}+{x}+{y}') + + def setup_password(self): + """设置密码""" + password = self.password_var.get().strip() + confirm_password = self.confirm_password_var.get().strip() + + if not password or not confirm_password: + messagebox.showerror("错误", "请输入密码和确认密码") + return + + if password != confirm_password: + messagebox.showerror("错误", "两次输入的密码不一致") + return + + # 注册用户 + success, message = self.user_manager.register_user(self.email, password) + + if success: + messagebox.showinfo("成功", message) + self.window.destroy() + # 返回登录窗口 + login_window = LoginWindow() + else: + messagebox.showerror("错误", message) diff --git a/src/ui/register_window.py b/src/ui/register_window.py new file mode 100644 index 0000000..eac1aa8 --- /dev/null +++ b/src/ui/register_window.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +注册窗口 +处理用户注册功能 +""" + +import tkinter as tk +from tkinter import messagebox, ttk +import sys +import os + +# 添加父目录到路径 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from user_manager import UserManager +from ui.password_setup_window import PasswordSetupWindow + +class RegisterWindow: + """注册窗口类""" + + def __init__(self): + self.user_manager = UserManager() + self.setup_ui() + + def setup_ui(self): + """设置UI界面""" + self.window = tk.Tk() + self.window.title("数学学习软件 - 注册") + self.window.geometry("400x250") + self.window.resizable(False, False) + + # 居中显示 + self.center_window() + + # 创建主框架 + main_frame = ttk.Frame(self.window, padding="20") + main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + + # 标题 + title_label = ttk.Label(main_frame, text="用户注册", font=("Arial", 16, "bold")) + title_label.grid(row=0, column=0, columnspan=2, pady=(0, 20)) + + # 邮箱输入 + ttk.Label(main_frame, text="邮箱:").grid(row=1, column=0, sticky=tk.W, pady=5) + self.email_var = tk.StringVar() + self.email_entry = ttk.Entry(main_frame, textvariable=self.email_var, width=30) + self.email_entry.grid(row=1, column=1, pady=5, padx=(10, 0)) + + # 验证码输入 + ttk.Label(main_frame, text="验证码:").grid(row=2, column=0, sticky=tk.W, pady=5) + self.code_var = tk.StringVar() + self.code_entry = ttk.Entry(main_frame, textvariable=self.code_var, width=20) + self.code_entry.grid(row=2, column=1, pady=5, padx=(10, 0), sticky=tk.W) + + # 获取验证码按钮 + self.get_code_btn = ttk.Button(main_frame, text="获取验证码", command=self.get_verification_code) + self.get_code_btn.grid(row=2, column=1, pady=5, padx=(10, 0), sticky=tk.E) + + # 按钮框架 + button_frame = ttk.Frame(main_frame) + button_frame.grid(row=3, column=0, columnspan=2, pady=20) + + # 注册按钮 + register_btn = ttk.Button(button_frame, text="注册", command=self.register) + register_btn.grid(row=0, column=0, padx=5) + + # 返回登录按钮 + back_btn = ttk.Button(button_frame, text="返回登录", command=self.back_to_login) + back_btn.grid(row=0, column=1, padx=5) + + # 绑定回车键 + self.window.bind('', lambda e: self.register()) + + # 设置焦点 + self.email_entry.focus() + + def center_window(self): + """窗口居中显示""" + self.window.update_idletasks() + width = self.window.winfo_width() + height = self.window.winfo_height() + x = (self.window.winfo_screenwidth() // 2) - (width // 2) + y = (self.window.winfo_screenheight() // 2) - (height // 2) + self.window.geometry(f'{width}x{height}+{x}+{y}') + + def get_verification_code(self): + """获取验证码""" + email = self.email_var.get().strip() + + if not email: + messagebox.showerror("错误", "请输入邮箱") + return + + success, message = self.user_manager.send_verification_code(email) + + if success: + messagebox.showinfo("成功", message) + # 禁用获取验证码按钮5分钟 + self.get_code_btn.config(state='disabled') + self.window.after(300000, lambda: self.get_code_btn.config(state='normal')) # 5分钟后重新启用 + else: + messagebox.showerror("错误", message) + + def register(self): + """处理注册""" + email = self.email_var.get().strip() + code = self.code_var.get().strip() + + if not email or not code: + messagebox.showerror("错误", "请输入邮箱和验证码") + return + + # 验证验证码 + success, message = self.user_manager.verify_code(email, code) + + if success: + messagebox.showinfo("成功", message) + self.window.destroy() + # 打开密码设置窗口 + password_setup_window = PasswordSetupWindow(email) + else: + messagebox.showerror("错误", message) + + def back_to_login(self): + """返回登录窗口""" + self.window.destroy() + from ui.login_window import LoginWindow + login_window = LoginWindow() diff --git a/src/ui/result_window.py b/src/ui/result_window.py new file mode 100644 index 0000000..b35ef0d --- /dev/null +++ b/src/ui/result_window.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +结果窗口 +显示答题结果和分数 +""" + +import tkinter as tk +from tkinter import messagebox, ttk +import sys +import os + +# 添加父目录到路径 +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from ui.main_window import MainWindow + +class ResultWindow: + """结果窗口类""" + + def __init__(self, email, level, correct_count, total_count, percentage): + self.email = email + self.level = level + self.correct_count = correct_count + self.total_count = total_count + self.percentage = percentage + self.setup_ui() + + def setup_ui(self): + """设置UI界面""" + self.window = tk.Tk() + self.window.title("数学学习软件 - 答题结果") + self.window.geometry("500x400") + self.window.resizable(False, False) + + # 居中显示 + self.center_window() + + # 创建主框架 + main_frame = ttk.Frame(self.window, padding="30") + main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + + # 标题 + title_label = ttk.Label(main_frame, text="答题结果", + font=("Arial", 18, "bold")) + title_label.grid(row=0, column=0, columnspan=2, pady=(0, 30)) + + # 学段信息 + level_label = ttk.Label(main_frame, text=f"学段:{self.level}", + font=("Arial", 12)) + level_label.grid(row=1, column=0, columnspan=2, pady=5) + + # 分数显示 + score_frame = ttk.LabelFrame(main_frame, text="成绩", padding="20") + score_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=20) + + # 正确题数 + correct_label = ttk.Label(score_frame, text=f"正确题数:{self.correct_count}", + font=("Arial", 14)) + correct_label.grid(row=0, column=0, columnspan=2, pady=5) + + # 总题数 + total_label = ttk.Label(score_frame, text=f"总题数:{self.total_count}", + font=("Arial", 14)) + total_label.grid(row=1, column=0, columnspan=2, pady=5) + + # 百分比 + percentage_label = ttk.Label(score_frame, text=f"正确率:{self.percentage:.1f}%", + font=("Arial", 16, "bold")) + percentage_label.grid(row=2, column=0, columnspan=2, pady=10) + + # 评价 + evaluation = self.get_evaluation(self.percentage) + evaluation_label = ttk.Label(score_frame, text=evaluation, + font=("Arial", 12), foreground="blue") + evaluation_label.grid(row=3, column=0, columnspan=2, pady=5) + + # 按钮框架 + button_frame = ttk.Frame(main_frame) + button_frame.grid(row=3, column=0, columnspan=2, pady=30) + + # 继续做题按钮 + continue_btn = ttk.Button(button_frame, text="继续做题", + command=self.continue_exam, style="Large.TButton") + continue_btn.grid(row=0, column=0, padx=10) + + # 返回主菜单按钮 + back_btn = ttk.Button(button_frame, text="返回主菜单", + command=self.back_to_main) + back_btn.grid(row=0, column=1, padx=10) + + # 退出按钮 + exit_btn = ttk.Button(button_frame, text="退出", + command=self.exit_app) + exit_btn.grid(row=0, column=2, padx=10) + + # 配置大按钮样式 + style = ttk.Style() + style.configure("Large.TButton", font=("Arial", 12, "bold")) + + def center_window(self): + """窗口居中显示""" + self.window.update_idletasks() + width = self.window.winfo_width() + height = self.window.winfo_height() + x = (self.window.winfo_screenwidth() // 2) - (width // 2) + y = (self.window.winfo_screenheight() // 2) - (height // 2) + self.window.geometry(f'{width}x{height}+{x}+{y}') + + def get_evaluation(self, percentage): + """根据百分比获取评价""" + if percentage >= 90: + return "优秀!继续保持!" + elif percentage >= 80: + return "良好!继续努力!" + elif percentage >= 70: + return "及格,需要加强练习!" + elif percentage >= 60: + return "需要更多练习!" + else: + return "加油!多练习会更好!" + + def continue_exam(self): + """继续做题""" + self.window.destroy() + from ui.level_selection_window import LevelSelectionWindow + level_selection_window = LevelSelectionWindow(self.email, self.level) + + def back_to_main(self): + """返回主菜单""" + self.window.destroy() + main_window = MainWindow(self.email) + + def exit_app(self): + """退出应用""" + if messagebox.askyesno("确认", "确定要退出吗?"): + self.window.destroy() + sys.exit(0) diff --git a/src/user_manager.py b/src/user_manager.py new file mode 100644 index 0000000..58d0f68 --- /dev/null +++ b/src/user_manager.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +用户管理模块 +处理用户注册、登录、密码管理等功能 +""" + +import json +import os +import re +import random +import string +from datetime import datetime, timedelta + +class UserManager: + """用户管理类""" + + def __init__(self): + self.users_file = "users.json" + self.verification_codes = {} # 存储验证码 {email: {'code': code, 'expire_time': time}} + self.load_users() + + def load_users(self): + """加载用户数据""" + if os.path.exists(self.users_file): + try: + with open(self.users_file, 'r', encoding='utf-8') as f: + self.users = json.load(f) + except: + self.users = {} + else: + self.users = {} + + def save_users(self): + """保存用户数据""" + try: + with open(self.users_file, 'w', encoding='utf-8') as f: + json.dump(self.users, f, ensure_ascii=False, indent=2) + return True + except Exception as e: + print(f"保存用户数据失败: {e}") + return False + + def generate_verification_code(self): + """生成6位数字验证码""" + return ''.join(random.choices(string.digits, k=6)) + + def send_verification_code(self, email): + """发送验证码到邮箱(模拟)""" + if not self.is_valid_email(email): + return False, "邮箱格式不正确" + + # 检查邮箱是否已注册 + if email in self.users: + return False, "该邮箱已注册" + + # 生成验证码 + code = self.generate_verification_code() + expire_time = datetime.now() + timedelta(minutes=5) # 5分钟过期 + + # 存储验证码 + self.verification_codes[email] = { + 'code': code, + 'expire_time': expire_time + } + + # 模拟发送邮件(实际项目中这里会调用邮件服务) + print(f"验证码已发送到 {email}: {code}") + + return True, f"验证码已发送到 {email},请查收邮件" + + def verify_code(self, email, code): + """验证验证码""" + if email not in self.verification_codes: + return False, "请先获取验证码" + + stored_data = self.verification_codes[email] + + # 检查是否过期 + if datetime.now() > stored_data['expire_time']: + del self.verification_codes[email] + return False, "验证码已过期,请重新获取" + + # 验证码是否正确 + if code != stored_data['code']: + return False, "验证码错误" + + # 验证成功,删除验证码 + del self.verification_codes[email] + return True, "验证码正确" + + def register_user(self, email, password): + """注册用户""" + if not self.is_valid_email(email): + return False, "邮箱格式不正确" + + if email in self.users: + return False, "该邮箱已注册" + + if not self.is_valid_password(password): + return False, "密码格式不正确,密码需6-10位,包含大小写字母和数字" + + # 创建用户记录 + self.users[email] = { + 'password': password, + 'register_time': datetime.now().isoformat(), + 'last_login': None + } + + # 保存到文件 + if self.save_users(): + return True, "注册成功" + else: + return False, "注册失败,请重试" + + def login(self, email, password): + """用户登录""" + if email not in self.users: + return False, "用户不存在" + + if self.users[email]['password'] != password: + return False, "密码错误" + + # 更新最后登录时间 + self.users[email]['last_login'] = datetime.now().isoformat() + self.save_users() + + return True, "登录成功" + + def change_password(self, email, old_password, new_password): + """修改密码""" + if email not in self.users: + return False, "用户不存在" + + if self.users[email]['password'] != old_password: + return False, "原密码错误" + + if not self.is_valid_password(new_password): + return False, "新密码格式不正确,密码需6-10位,包含大小写字母和数字" + + self.users[email]['password'] = new_password + + if self.save_users(): + return True, "密码修改成功" + else: + return False, "密码修改失败,请重试" + + def is_valid_email(self, email): + """验证邮箱格式""" + pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' + return re.match(pattern, email) is not None + + def is_valid_password(self, password): + """验证密码格式:6-10位,包含大小写字母和数字""" + if len(password) < 6 or len(password) > 10: + return False + + has_upper = any(c.isupper() for c in password) + has_lower = any(c.islower() for c in password) + has_digit = any(c.isdigit() for c in password) + + return has_upper and has_lower and has_digit