diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..9338fb5 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,113 @@ +# teamwork-project + +## 项目简介 +这是一个面向小学、初中和高中学生的数学学习软件,采用图形化界面操作,提供数学题目生成、在线考试、成绩记录等功能。项目基于C++和Qt框架开发,实现了完整的用户注册、登录、考试和成绩管理流程。 + +## 项目特点 +1.多学段支持:覆盖小学、初中、高中三个学段的数学题目 +2.安全认证:支持用户注册、登录、密码修改功能 +3.智能出题:根据学段自动生成相应难度的数学题目 +4.实时评分:自动批改试卷并显示详细成绩 +5.数据持久化:本地文件存储用户数据和考试记录 +6.邮箱验证:支持真实邮箱验证码发送功能 + +## 功能模块 + +## 用户管理 +1.用户注册:邮箱验证、密码强度校验 +2.用户登录:安全的身份认证 +3.密码修改:支持在线修改密码 + +## 考试系统 +1.题目生成:根据学段生成不同难度的选择题 +2.考试界面:清晰的题目展示和选项选择 +3.进度跟踪:实时显示答题进度 +4.成绩统计:自动计算得分和正确率 + +## 数据管理 +1.用户数据:本地文件存储用户信息 +2.考试记录:保存历史考试成绩 +3.题目文件:导出生成的题目试卷 + +## 技术架构 + +## 前端界面 +Qt Widgets:跨平台的图形界面框架 +响应式设计:自适应不同屏幕尺寸 +美观UI:现代化的界面风格设计 + +## 后端逻辑 +C++11/14:核心业务逻辑实现 +面向对象设计:模块化的类结构 +跨平台支持:Windows/Linux/macOS兼容 + +## 核心类说明 +## 前端类 +MainWindow:主窗口控制器 +LoginWidget:登录界面 +RegisterWidget:注册界面 +MainMenuWidget:主菜单界面 +ExamWidget:考试界面 +ResultWidget:成绩界面 + +## 后端类 +User:用户信息管理 +UserManager:用户认证和注册 +QuestionGenerator:题目生成器 +FileSaver:文件存储管理 + +## 项目结构 +teamwork-project/ +├── src/ # 源代码目录 +│ ├── frontend/ # 前端代码目录 +│ │ ├── mathlearningApp.pro # Qt项目配置文件 +│ │ ├── main.cpp # 程序入口 +│ │ ├── mainwindow.h/cpp # 主窗口控制器 +│ │ ├── loginwidget.h/cpp # 登录界面 +│ │ ├── registerwidget.h/cpp # 注册界面 +│ │ ├── mainmenuwidget.h/cpp # 主菜单界面 +│ │ ├── examwidget.h/cpp # 考试界面 +│ │ └── resultwidget.h/cpp # 成绩界面 +│ └── backend/ # 后端代码目录 +│ ├── user.h/cpp # 用户类 +│ ├── usermanage.h/cpp # 用户管理 +│ ├── questiongenerator.h/cpp # 题目生成器 +│ └── filesaver.h/cpp # 文件存储 +└── doc/ # 文档目录 + └── README.md # 项目说明 + +## 编译运行 +双击.exe文件运行 + +## 环境要求 +Qt 5.12 或更高版本 +C++11 兼容编译器 +CMake 3.10 或更高版本 + +## 使用说明 +## 首次使用 +运行程序,进入登录界面 +点击"注册新账号"创建用户账户 +输入邮箱获取验证码完成注册 +设置符合要求的密码(6-10位,包含大小写字母和数字) + +## 参加考试 +登录成功后进入主菜单 +选择考试年级和题目数量(5-30题) +点击"开始考试"进入考试界面 +逐题作答,可前后翻页检查 +提交后查看成绩和正确率 + +## 架构设计说明 +## 前后端分离 +前端:负责UI展示、用户交互、界面逻辑 +后端:负责业务逻辑、数据处理、算法实现 +通信方式:通过类接口和信号槽机制进行通信 + +## 设计模式 +MVC模式:清晰的数据-视图-控制器分离 +观察者模式:通过Qt信号槽实现组件间通信 +单例模式:用户管理等核心服务 + +## 问题反馈 +如在使用过程中遇到问题,请联系开发团队或提交Issue。 \ No newline at end of file diff --git a/src/backend/filesaver.cpp b/src/backend/filesaver.cpp new file mode 100644 index 0000000..5c7ad2f --- /dev/null +++ b/src/backend/filesaver.cpp @@ -0,0 +1,205 @@ +#include "filesaver.h" +#include +#include +#include +#include +#include + +//创建目录(跨平台实现) +bool FileSaver::createDirectory(const std::wstring& wpath) { +#ifdef _WIN32 + return _wmkdir(wpath.c_str()) == 0; +#else + std::string path; + for (wchar_t wc : wpath) { + if (wc < 128) path += static_cast(wc); + } + return mkdir(path.c_str(), 0755) == 0; +#endif +} + +//将包含中文的用户名转换为安全的英文文件名 +std::string FileSaver::usernameToSafeName(const std::wstring& username) { + std::string result; + for (wchar_t wc : username) { + if ((wc >= L'0' && wc <= L'9') || (wc >= L'A' && wc <= L'Z') || + (wc >= L'a' && wc <= L'z') || wc == L'_') { + result += static_cast(wc); + } else { + switch(wc) { + case L'张': result += "Zhang"; break; + case L'三': result += "San"; break; + case L'李': result += "Li"; break; + case L'四': result += "Si"; break; + case L'王': result += "Wang"; break; + case L'五': result += "Wu"; break; + default: result += "User"; break; + } + } + } + return result; +} + +//将中文年级转换为英文标识 +std::string FileSaver::gradeToEnglish(const std::wstring& grade) { + if (grade == L"小学") return "Primary"; + if (grade == L"初中") return "Junior"; + if (grade == L"高中") return "Senior"; + return "Unknown"; +} + +//宽字符串到普通字符串的转换 +std::string FileSaver::wstringToString(const std::wstring& wstr) { + std::string result; + for (wchar_t wc : wstr) { + if (wc < 128) { + result += static_cast(wc); + } + } + return result; +} + +//将题目列表写入文件的实现 +bool FileSaver::writeQuestionsToFile(const std::string& filename, + const std::string& safeUsername, + const std::string& englishGrade, + const std::vector& questions) { + std::ofstream file(filename); + if (!file.is_open()) { + return false; + } + + file << "Math Exam Paper\n"; + file << "Username: " << safeUsername << "\n"; + file << "Grade: " << englishGrade << "\n"; + file << "Time: " << wstringToString(getCurrentTime()) << "\n\n"; + + for (size_t i = 0; i < questions.size(); i++) { + file << (i + 1) << ". "; + + for (wchar_t wc : questions[i]) { + switch(wc) { + case L'√': file << "√"; break; + case L'²': file << "²"; break; + case L's': file << "s"; break; + case L'i': file << "i"; break; + case L'n': file << "n"; break; + case L'c': file << "c"; break; + case L'o': file << "o"; break; + case L't': file << "t"; break; + case L'a': file << "a"; break; + case L'(': file << "("; break; + case L')': file << ")"; break; + case L'+': file << "+"; break; + case L'-': file << "-"; break; + case L'×': file << "×"; break; + case L'÷': file << "÷"; break; + case L'=': file << "="; break; + case L'?': file << "?"; break; + case L' ': file << " "; break; + case L'0': case L'1': case L'2': case L'3': case L'4': + case L'5': case L'6': case L'7': case L'8': case L'9': + file << static_cast(wc); + break; + default: file << static_cast(wc); break; + } + } + file << "\n\n"; + } + + file.close(); + return true; +} + +//准备文件和目录的实现 +std::string FileSaver::prepareFileAndDirectory(const std::wstring& username, + const std::wstring& grade) { + std::string safeUsername = usernameToSafeName(username); + std::string englishGrade = gradeToEnglish(grade); + std::string folderPath = "exam_papers/" + safeUsername + "_" + englishGrade + "_Papers"; + + // 创建目录 + std::wstring wfolderPath; + for (char c : folderPath) { + wfolderPath += wchar_t(c); + } + createDirectory(wfolderPath); + + return folderPath + "/" + wstringToString(generateFilename()) + ".txt"; +} + +//保存题目的完整流程实现 +bool FileSaver::saveToFile(const std::wstring& username, + const std::wstring& grade, + const std::vector& questions) { + std::string safeUsername = usernameToSafeName(username); + std::string englishGrade = gradeToEnglish(grade); + std::string filename = prepareFileAndDirectory(username, grade); + + bool success = writeQuestionsToFile(filename, safeUsername, englishGrade, questions); + + return success; +} + +//生成基于时间戳的文件名实现 +std::wstring FileSaver::generateFilename() { + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + + std::tm tm = *std::localtime(&time_t); + std::wostringstream oss; + + oss << tm.tm_year + 1900 << L"_" + << (tm.tm_mon + 1 < 10 ? L"0" : L"") << tm.tm_mon + 1 << L"_" + << (tm.tm_mday < 10 ? L"0" : L"") << tm.tm_mday << L"_" + << (tm.tm_hour < 10 ? L"0" : L"") << tm.tm_hour << L"_" + << (tm.tm_min < 10 ? L"0" : L"") << tm.tm_min << L"_" + << (tm.tm_sec < 10 ? L"0" : L"") << tm.tm_sec; + + return oss.str(); +} + +//获取当前格式化时间的实现 +std::wstring FileSaver::getCurrentTime() { + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + + std::tm tm = *std::localtime(&time_t); + std::wostringstream oss; + + oss << tm.tm_year + 1900 << L"-" + << (tm.tm_mon + 1 < 10 ? L"0" : L"") << tm.tm_mon + 1 << L"-" + << (tm.tm_mday < 10 ? L"0" : L"") << tm.tm_mday << L" " + << (tm.tm_hour < 10 ? L"0" : L"") << tm.tm_hour << L":" + << (tm.tm_min < 10 ? L"0" : L"") << tm.tm_min << L":" + << (tm.tm_sec < 10 ? L"0" : L"") << tm.tm_sec; + + return oss.str(); +} + +//保存考试记录的实现 +bool FileSaver::saveExamRecord(const std::wstring& username, + const std::wstring& grade, + int score, + int totalQuestions, + const std::wstring& examDate) { + std::string safeUsername = usernameToSafeName(username); + std::string filename = "exam_records/" + safeUsername + "_records.txt"; + + // 创建目录 + createDirectory(L"exam_records"); + + std::ofstream file(filename, std::ios::app); + if (!file.is_open()) { + return false; + } + + file << "Date: " << wstringToString(examDate) << "\n"; + file << "Grade: " << gradeToEnglish(grade) << "\n"; + file << "Score: " << score << "/" << totalQuestions << "\n"; + file << "Percentage: " << (score * 100.0 / totalQuestions) << "%\n"; + file << "------------------------\n"; + + file.close(); + return true; +} diff --git a/src/backend/filesaver.h b/src/backend/filesaver.h new file mode 100644 index 0000000..2599b4e --- /dev/null +++ b/src/backend/filesaver.h @@ -0,0 +1,63 @@ +#ifndef FILESAVER_H +#define FILESAVER_H + +#include +#include +#include +#include +#include + +#ifdef _WIN32 + #include + #include +#else + #include + #include +#endif + +// 文件保存器类,负责将生成的数学题目保存到文件系统 +class FileSaver { +public: + //将题目列表写入指定文件 + static bool writeQuestionsToFile(const std::string& filename, + const std::string& safeUsername, + const std::string& englishGrade, + const std::vector& questions); + + //准备用户专属文件夹和文件路径 + static std::string prepareFileAndDirectory(const std::wstring& username, + const std::wstring& grade); + + //保存题目列表到用户专属文件 + static bool saveToFile(const std::wstring& username, + const std::wstring& grade, + const std::vector& questions); + + //生成基于当前时间戳的文件名 + static std::wstring generateFilename(); + + //获取当前格式化的时间字符串 + static std::wstring getCurrentTime(); + + //保存考试记录 + static bool saveExamRecord(const std::wstring& username, + const std::wstring& grade, + int score, + int totalQuestions, + const std::wstring& examDate); + +private: + //将包含中文的用户名转换为安全的英文文件名 + static std::string usernameToSafeName(const std::wstring& username); + + //将中文年级转换为英文标识 + static std::string gradeToEnglish(const std::wstring& grade); + + //宽字符串到普通字符串的转换 + static std::string wstringToString(const std::wstring& wstr); + + //创建目录(跨平台实现) + static bool createDirectory(const std::wstring& wpath); +}; + +#endif \ No newline at end of file diff --git a/src/backend/questiongenerator.cpp b/src/backend/questiongenerator.cpp new file mode 100644 index 0000000..358499e --- /dev/null +++ b/src/backend/questiongenerator.cpp @@ -0,0 +1,596 @@ +#include "questiongenerator.h" +#include +#include +#include +#include +#include +#include +#include +#include + +//题目生成器构造函数的实现 +QuestionGenerator::QuestionGenerator() : currentGrade(Grade::PRIMARY), gen(rd()) {} + +//设置年级的实现 +void QuestionGenerator::setGrade(Grade grade) { + currentGrade = grade; + qDebug() << "QuestionGenerator: 设置年级为:" << static_cast(grade); +} + +//生成随机数的实现 +std::wstring QuestionGenerator::generateNumber(int min, int max) { + std::uniform_int_distribution<> dis(min, max); + return std::to_wstring(dis(gen)); +} + +//生成随机运算符的实现 +std::wstring QuestionGenerator::generateOperator() { + std::vector operators = {L"+", L"-", L"×", L"÷"}; + std::uniform_int_distribution<> dis(0, static_cast(operators.size()) - 1); + return operators[dis(gen)]; +} + +//括号需求判断的辅助函数 +bool QuestionGenerator::needsParentheses(const std::wstring& expr, const std::wstring& nextOp) { + if (expr.find(L"+") != std::wstring::npos || expr.find(L"-") != std::wstring::npos) { + if (nextOp == L"×" || nextOp == L"÷") { + return true; + } + } + return false; +} + +//生成小学题目的实现 - 至少2个操作数 +std::wstring QuestionGenerator::generatePrimaryQuestion() { + std::uniform_int_distribution<> operandDis(2, 5); // 2-5个操作数(至少2个) + int operandCount = operandDis(gen); + std::wstring question; + std::vector numbers; + std::vector operators; + + // 生成操作数和运算符 + for (int i = 0; i < operandCount; i++) { + numbers.push_back(generateNumber(1, 100)); // 1-100 + if (i < operandCount - 1) { + operators.push_back(generateOperator()); + } + } + + // 构建表达式 + question = numbers[0]; + for (size_t i = 0; i < operators.size(); i++) { + // 简单的括号逻辑:在需要时添加括号 + if (i < operators.size() - 1 && (operators[i] == L"+" || operators[i] == L"-") && + (operators[i+1] == L"×" || operators[i+1] == L"÷")) { + question = L"(" + question + L")"; + } + question += L" " + operators[i] + L" " + numbers[i + 1]; + } + + return question + L" = ?"; +} + +//生成初中题目的实现 - 可以为1个操作数 +std::wstring QuestionGenerator::generateJuniorQuestion() { + std::uniform_int_distribution<> operandDis(1, 5); // 1-5个操作数(可以为1) + int operandCount = operandDis(gen); + std::uniform_int_distribution<> typeDis(0, 1); // 0:平方, 1:开根号 + + std::wstring question; + + // 如果只有一个操作数,直接生成平方或开根号题目 + if (operandCount == 1) { + int specialType = typeDis(gen); + if (specialType == 0) { + // 平方 + std::wstring number = generateNumber(1, 100); + question = number + L"² = ?"; + } else { + // 开根号 - 确保是平方数 + int num = std::uniform_int_distribution<>(1, 20)(gen); + int square = num * num; + question = L"√" + std::to_wstring(square) + L" = ?"; + } + return question; // 直接返回,不再添加 = ? + } else { + // 多个操作数的情况 + std::vector numbers; + std::vector operators; + + // 生成基础操作数和运算符 + for (int i = 0; i < operandCount; i++) { + numbers.push_back(generateNumber(1, 100)); // 1-100 + if (i < operandCount - 1) { + operators.push_back(generateOperator()); + } + } + + // 随机选择一个位置插入平方或开根号 + int specialPos = std::uniform_int_distribution<>(0, operandCount - 1)(gen); + int specialType = typeDis(gen); + + // 构建表达式 + question = numbers[0]; + for (size_t i = 0; i < operators.size(); i++) { + // 在指定位置插入特殊运算符 + if (static_cast(i) == specialPos) { + if (specialType == 0) { + // 平方 + question = L"(" + question + L")²"; + } else { + // 开根号 - 确保是平方数 + int num = std::uniform_int_distribution<>(1, 20)(gen); + int square = num * num; + question = L"√" + std::to_wstring(square) + L" " + operators[i] + L" " + question; + } + } else { + // 简单的括号逻辑 + if (i < operators.size() - 1 && (operators[i] == L"+" || operators[i] == L"-") && + (operators[i+1] == L"×" || operators[i+1] == L"÷")) { + question = L"(" + question + L")"; + } + question += L" " + operators[i] + L" " + numbers[i + 1]; + } + } + + // 如果特殊运算符在最后一个位置 + if (specialPos == static_cast(operators.size())) { + if (specialType == 0) { + question = L"(" + question + L")²"; + } else { + int num = std::uniform_int_distribution<>(1, 20)(gen); + int square = num * num; + question = L"√" + std::to_wstring(square) + L" " + operators.back() + L" " + question; + } + } + + return question + L" = ?"; // 只有多个操作数时才在这里添加 = ? + } +} + +//生成高中题目的实现 - 可以为1个操作数 +std::wstring QuestionGenerator::generateSeniorQuestion() { + std::uniform_int_distribution<> operandDis(1, 5); // 1-5个操作数(可以为1) + int operandCount = operandDis(gen); + std::vector trigFunctions = {L"sin", L"cos", L"tan"}; + + std::wstring question; + + // 如果只有一个操作数,直接生成三角函数题目 + if (operandCount == 1) { + int trigType = std::uniform_int_distribution<>(0, 2)(gen); + int angle = std::uniform_int_distribution<>(0, 90)(gen); // 角度范围0-90度 + question = trigFunctions[trigType] + L"(" + std::to_wstring(angle) + L"°) = ?"; + return question; // 直接返回,不再添加 = ? + } else { + // 多个操作数的情况 + std::vector numbers; + std::vector operators; + + // 生成基础操作数和运算符 + for (int i = 0; i < operandCount; i++) { + numbers.push_back(generateNumber(1, 100)); // 1-100 + if (i < operandCount - 1) { + operators.push_back(generateOperator()); + } + } + + // 随机选择一个位置插入三角函数 + int trigPos = std::uniform_int_distribution<>(0, operandCount - 1)(gen); + int trigType = std::uniform_int_distribution<>(0, 2)(gen); + int angle = std::uniform_int_distribution<>(0, 90)(gen); // 角度范围0-90度 + + // 构建表达式 + question = numbers[0]; + for (size_t i = 0; i < operators.size(); i++) { + // 在指定位置插入三角函数 + if (static_cast(i) == trigPos) { + std::wstring trigExpr = trigFunctions[trigType] + L"(" + std::to_wstring(angle) + L"°)"; + question = trigExpr + L" " + operators[i] + L" " + question; + } else { + // 简单的括号逻辑 + if (i < operators.size() - 1 && (operators[i] == L"+" || operators[i] == L"-") && + (operators[i+1] == L"×" || operators[i+1] == L"÷")) { + question = L"(" + question + L")"; + } + question += L" " + operators[i] + L" " + numbers[i + 1]; + } + } + + // 如果三角函数在最后一个位置 + if (trigPos == static_cast(operators.size())) { + std::wstring trigExpr = trigFunctions[trigType] + L"(" + std::to_wstring(angle) + L"°)"; + question = trigExpr + L" " + operators.back() + L" " + question; + } + + return question + L" = ?"; // 只有多个操作数时才在这里添加 = ? + } +} + +//题目查重的实现 +bool QuestionGenerator::IsQuestionUnique(const std::wstring& question) { + return generatedQuestions.find(question) == generatedQuestions.end(); +} + +//生成指定数量题目的实现 +std::vector QuestionGenerator::generateQuestions(int count) { + // 验证题目数量范围 + if (count < 10 || count > 30) { + qDebug() << "QuestionGenerator: 题目数量超出范围(10-30),使用默认值10"; + count = 10; + } + + std::vector questions; + int attempts = 0; + const int maxAttempts = count * 10; // 增加尝试次数 + + for (int i = 0; i < count && attempts < maxAttempts; i++) { + std::wstring question; + + switch(currentGrade) { + case Grade::PRIMARY: + question = generatePrimaryQuestion(); + break; + case Grade::JUNIOR: + question = generateJuniorQuestion(); + break; + case Grade::SENIOR: + question = generateSeniorQuestion(); + break; + } + + if (IsQuestionUnique(question)) { + questions.push_back(question); + generatedQuestions.insert(question); + qDebug() << "生成题目" << i + 1 << ":" << QString::fromStdWString(question); + } else { + i--; + qDebug() << "题目重复,重新生成"; + } + attempts++; + } + + if (static_cast(questions.size()) < count) { + qDebug() << "QuestionGenerator: 警告:只生成了" << questions.size() << "个唯一题目"; + } + + qDebug() << "QuestionGenerator: 成功生成" << questions.size() << "个题目"; + return questions; +} + +// 计算小学表达式结果 - 改进版本 +double QuestionGenerator::calculatePrimaryExpression(const std::wstring& expr) { + try { + // 简单的表达式计算,支持基础运算 + std::wstring expression = expr; + + // 移除空格 + expression.erase(std::remove(expression.begin(), expression.end(), L' '), expression.end()); + + // 简单的递归计算函数 + std::function calculate = [&](const std::wstring& str) -> double { + if (str.empty()) return 0.0; + + // 处理括号 + if (str.front() == L'(' && str.back() == L')') { + return calculate(str.substr(1, str.length() - 2)); + } + + // 查找最低优先级的运算符 + int parenCount = 0; + int lowestPriority = -1; + int lowestPos = -1; + + for (int i = static_cast(str.length()) - 1; i >= 0; i--) { + wchar_t c = str[i]; + if (c == L')') parenCount++; + else if (c == L'(') parenCount--; + else if (parenCount == 0) { + if ((c == L'+' || c == L'-') && lowestPriority < 1) { + lowestPriority = 1; + lowestPos = i; + } else if ((c == L'×' || c == L'÷') && lowestPriority < 0) { + lowestPriority = 0; + lowestPos = i; + } + } + } + + if (lowestPos != -1) { + std::wstring left = str.substr(0, lowestPos); + std::wstring right = str.substr(lowestPos + 1); + wchar_t op = str[lowestPos]; + + double leftVal = calculate(left); + double rightVal = calculate(right); + + switch(op) { + case L'+': return leftVal + rightVal; + case L'-': return leftVal - rightVal; + case L'×': return leftVal * rightVal; + case L'÷': + if (rightVal == 0) { + qDebug() << "除零错误"; + return 0.0; + } + return leftVal / rightVal; + } + } + + // 没有运算符,直接解析数字 + try { + return std::stod(str); + } catch (...) { + qDebug() << "无法解析数字:" << QString::fromStdWString(str); + return 0.0; + } + }; + + return calculate(expression); + } catch (const std::exception& e) { + qDebug() << "计算小学表达式异常:" << e.what(); + return 0.0; + } +} + +// 计算初中表达式结果 - 改进版本 +double QuestionGenerator::calculateJuniorExpression(const std::wstring& expr) { + try { + std::wstring expression = expr; + + // 处理平方运算 + size_t squarePos = expression.find(L"²"); + while (squarePos != std::wstring::npos) { + // 找到平方的基数 + int start = static_cast(squarePos) - 1; + while (start >= 0 && (std::iswdigit(expression[start]) || expression[start] == L')')) { + start--; + } + if (start >= 0 && expression[start] == L'(') start--; + + std::wstring baseStr = expression.substr(start + 1, squarePos - start - 1); + double base = calculatePrimaryExpression(baseStr); + double result = base * base; + + // 替换平方部分 + expression.replace(start + 1, squarePos - start + 1, std::to_wstring(result)); + squarePos = expression.find(L"²"); + } + + // 处理开方运算 + size_t sqrtPos = expression.find(L"√"); + while (sqrtPos != std::wstring::npos) { + // 找到被开方数 + size_t end = sqrtPos + 1; + while (end < expression.length() && (std::iswdigit(expression[end]) || expression[end] == L'(')) { + end++; + } + + std::wstring radicandStr = expression.substr(sqrtPos + 1, end - sqrtPos - 1); + double radicand = calculatePrimaryExpression(radicandStr); + if (radicand < 0) { + qDebug() << "负数开方错误"; + return 0.0; + } + double result = std::sqrt(radicand); + + // 替换开方部分 + expression.replace(sqrtPos, end - sqrtPos, std::to_wstring(result)); + sqrtPos = expression.find(L"√"); + } + + return calculatePrimaryExpression(expression); + } catch (const std::exception& e) { + qDebug() << "计算初中表达式异常:" << e.what(); + return 0.0; + } +} + +// 计算高中表达式结果 - 改进版本 +double QuestionGenerator::calculateSeniorExpression(const std::wstring& expr) { + try { + std::wstring expression = expr; + + // 处理三角函数 + std::vector trigFuncs = {L"sin", L"cos", L"tan"}; + + for (const auto& func : trigFuncs) { + size_t pos = expression.find(func); + while (pos != std::wstring::npos) { + // 找到括号内的角度 + size_t parenStart = pos + func.length(); + if (parenStart >= expression.length() || expression[parenStart] != L'(') { + break; + } + + size_t parenEnd = expression.find(L')', parenStart); + if (parenEnd == std::wstring::npos) { + break; + } + + std::wstring angleStr = expression.substr(parenStart + 1, parenEnd - parenStart - 1); + // 移除度符号 + if (!angleStr.empty() && angleStr.back() == L'°') { + angleStr.pop_back(); + } + + double angle = std::stod(angleStr); + double result = 0.0; + + // 转换为弧度并计算 + double radians = angle * M_PI / 180.0; + if (func == L"sin") { + result = std::sin(radians); + } else if (func == L"cos") { + result = std::cos(radians); + } else if (func == L"tan") { + if (std::abs(std::cos(radians)) < 1e-10) { + qDebug() << "tan函数计算错误,角度:" << angle; + return 0.0; + } + result = std::tan(radians); + } + + // 替换三角函数部分 + expression.replace(pos, parenEnd - pos + 1, std::to_wstring(result)); + pos = expression.find(func); + } + } + + return calculateJuniorExpression(expression); + } catch (const std::exception& e) { + qDebug() << "计算高中表达式异常:" << e.what(); + return 0.0; + } +} + +// 计算表达式结果 +double QuestionGenerator::calculateExpression(const std::wstring& expression) { + try { + switch(currentGrade) { + case Grade::PRIMARY: + return calculatePrimaryExpression(expression); + case Grade::JUNIOR: + return calculateJuniorExpression(expression); + case Grade::SENIOR: + return calculateSeniorExpression(expression); + default: + return 0.0; + } + } catch (const std::exception& e) { + qDebug() << "计算表达式异常:" << e.what(); + return 0.0; + } +} + +// 计算正确答案 +double QuestionGenerator::calculateCorrectAnswer(const std::wstring& question) { + try { + // 移除 "= ?" 部分 + std::wstring expression = question; + size_t pos = expression.find(L" = ?"); + if (pos != std::wstring::npos) { + expression = expression.substr(0, pos); + } + + qDebug() << "计算题目答案:" << QString::fromStdWString(expression); + double result = calculateExpression(expression); + qDebug() << "计算结果:" << result; + return result; + } catch (const std::exception& e) { + qDebug() << "计算正确答案异常:" << e.what(); + return 0.0; + } +} + +// 生成有意义的选项 +std::vector QuestionGenerator::generateMeaningfulOptions(double correctAnswer) { + try { + std::vector options; + std::uniform_real_distribution dis(-2.0, 2.0); + std::set usedValues; + + // 格式化正确答案 + std::stringstream oss; + oss << std::fixed << std::setprecision(6) << correctAnswer; + std::string correctStr = oss.str(); + // 移除末尾多余的0 + correctStr.erase(correctStr.find_last_not_of('0') + 1, std::string::npos); + if (!correctStr.empty() && correctStr.back() == '.') { + correctStr.pop_back(); + } + + std::wstring wcorrectStr(correctStr.begin(), correctStr.end()); + options.push_back(wcorrectStr); + usedValues.insert(correctAnswer); + + // 生成有意义的干扰项 + for (int i = 0; i < 3; ++i) { + double variation = dis(gen); + double wrongAnswer = correctAnswer + variation; + + // 确保干扰项与正确答案不同且唯一 + int attempts = 0; + while ((std::abs(wrongAnswer - correctAnswer) < 0.001 || + usedValues.find(wrongAnswer) != usedValues.end()) && + attempts < 10) { + variation = dis(gen); + wrongAnswer = correctAnswer + variation; + attempts++; + } + + usedValues.insert(wrongAnswer); + + std::stringstream wrongOss; + wrongOss << std::fixed << std::setprecision(6) << wrongAnswer; + std::string wrongStr = wrongOss.str(); + // 移除末尾多余的0 + wrongStr.erase(wrongStr.find_last_not_of('0') + 1, std::string::npos); + if (!wrongStr.empty() && wrongStr.back() == '.') { + wrongStr.pop_back(); + } + + std::wstring wwrongStr(wrongStr.begin(), wrongStr.end()); + options.push_back(wwrongStr); + } + + // 随机打乱选项顺序 + std::shuffle(options.begin(), options.end(), gen); + + return options; + } catch (const std::exception& e) { + qDebug() << "生成选项异常:" << e.what(); + // 返回默认选项 + return {L"1.0", L"2.0", L"3.0", L"4.0"}; + } +} + +//生成题目和选项的实现(用于GUI考试) +QuestionGenerator::QuestionWithOptions QuestionGenerator::generateQuestionWithOptions() { + QuestionWithOptions qwo; + + try { + // 生成题目 + switch(currentGrade) { + case Grade::PRIMARY: + qwo.question = generatePrimaryQuestion(); + break; + case Grade::JUNIOR: + qwo.question = generateJuniorQuestion(); + break; + case Grade::SENIOR: + qwo.question = generateSeniorQuestion(); + break; + } + + // 计算正确答案并生成有意义的选项 + double correctAnswer = calculateCorrectAnswer(qwo.question); + qwo.options = generateMeaningfulOptions(correctAnswer); + + // 找到正确答案的索引 + std::stringstream oss; + oss << std::fixed << std::setprecision(6) << correctAnswer; + std::string correctStr = oss.str(); + std::wstring wcorrectStr(correctStr.begin(), correctStr.end()); + + for (size_t i = 0; i < qwo.options.size(); ++i) { + if (qwo.options[i] == wcorrectStr) { + qwo.correctIndex = static_cast(i); + break; + } + } + } catch (const std::exception& e) { + qDebug() << "生成题目和选项异常:" << e.what(); + // 返回默认题目 + qwo.question = L"1 + 1 = ?"; + qwo.options = {L"1.0", L"2.0", L"3.0", L"4.0"}; + qwo.correctIndex = 1; + } + + return qwo; +} + +//清空历史记录的实现 +void QuestionGenerator::clearHistory() { + generatedQuestions.clear(); +} diff --git a/src/backend/questiongenerator.h b/src/backend/questiongenerator.h new file mode 100644 index 0000000..937ee1b --- /dev/null +++ b/src/backend/questiongenerator.h @@ -0,0 +1,78 @@ +#ifndef QUESTIONGENERATOR_H +#define QUESTIONGENERATOR_H + +#include "user.h" +#include +#include +#include +#include +#include +#include +#include + +//题目生成器类,根据年级生成不同难度的数学题目 +//支持小学、初中、高中三个级别的题目生成,包含查重功能。 +class QuestionGenerator { +private: + Grade currentGrade; + std::set generatedQuestions; + std::random_device rd; + std::mt19937 gen; + + //生成小学级别的基础算术题目 + std::wstring generatePrimaryQuestion(); + + //生成初中级别的题目,包含平方或开根号 + std::wstring generateJuniorQuestion(); + + //生成高中级别的题目,包含三角函数 + std::wstring generateSeniorQuestion(); + + //生成指定范围内的随机整数 + std::wstring generateNumber(int min, int max); + + //随机生成算术运算符 + std::wstring generateOperator(); + + //检查题目是否已生成过(查重) + bool IsQuestionUnique(const std::wstring& question); + + //括号需求判断 + bool needsParentheses(const std::wstring& expr, const std::wstring& nextOp); + + // 计算表达式结果 + double calculateExpression(const std::wstring& expression); + double calculatePrimaryExpression(const std::wstring& expr); + double calculateJuniorExpression(const std::wstring& expr); + double calculateSeniorExpression(const std::wstring& expr); + +public: + //构造函数,初始化题目生成器 + QuestionGenerator(); + + //设置当前题目生成难度级别 + void setGrade(Grade grade); + + //生成指定数量的唯一数学题目 + std::vector generateQuestions(int count); + + //生成题目和选项(用于GUI) + struct QuestionWithOptions { + std::wstring question; + std::vector options; + int correctIndex; + }; + + QuestionWithOptions generateQuestionWithOptions(); + + //清空已生成题目的历史记录 + void clearHistory(); + + // 计算正确答案 + double calculateCorrectAnswer(const std::wstring& question); + + // 生成有意义的选项 + std::vector generateMeaningfulOptions(double correctAnswer); +}; + +#endif diff --git a/src/backend/user.cpp b/src/backend/user.cpp new file mode 100644 index 0000000..bf80f15 --- /dev/null +++ b/src/backend/user.cpp @@ -0,0 +1,41 @@ +#include "user.h" +#include + +//用户构造函数的实现 +User::User(const std::wstring& name, const std::wstring& pass, Grade grade) + : username(name), password(pass), gradeType(grade) {} + +//用户验证的实现 +bool User::check(const std::wstring& name, const std::wstring& pass) const { + return (username == name && password == pass); +} + +//获取用户名的实现 +std::wstring User::getUsername() const { + return username; +} + +//获取密码的实现 +std::wstring User::getPassword() const { + return password; +} + +//获取年级枚举值的实现 +Grade User::getGrade() const { + return gradeType; +} + +//获取年级字符串的实现 +std::wstring User::getGradeString() const { + switch(gradeType) { + case Grade::PRIMARY: return L"小学"; + case Grade::JUNIOR: return L"初中"; + case Grade::SENIOR: return L"高中"; + default: return L"未知"; + } +} + +//设置密码的实现 +void User::setPassword(const std::wstring& newPassword) { + password = newPassword; +} \ No newline at end of file diff --git a/src/backend/user.h b/src/backend/user.h new file mode 100644 index 0000000..0d2ee60 --- /dev/null +++ b/src/backend/user.h @@ -0,0 +1,41 @@ +#ifndef USER_H +#define USER_H + +#include + +//年级枚举,表示用户所属的学段级别 +//用于区分不同学段的题目难度和生成规则 +enum class Grade { PRIMARY, JUNIOR, SENIOR }; + +//用户信息类,封装用户认证数据和年级信息 +//存储用户名、密码和年级类型,提供验证和查询接口。 +class User { +private: + std::wstring username; + std::wstring password; + Grade gradeType; + +public: + //构造函数,初始化用户信息 + User(const std::wstring& name, const std::wstring& pass, Grade grade); + + //验证用户名和密码是否匹配 + bool check(const std::wstring& name, const std::wstring& pass) const; + + //获取用户名 + std::wstring getUsername() const; + + //获取密码 + std::wstring getPassword() const; + + //获取年级枚举值 + Grade getGrade() const; + + //获取年级的中文描述 + std::wstring getGradeString() const; + + //设置密码 + void setPassword(const std::wstring& newPassword); +}; + +#endif \ No newline at end of file diff --git a/src/backend/usermanage.cpp b/src/backend/usermanage.cpp new file mode 100644 index 0000000..80c6070 --- /dev/null +++ b/src/backend/usermanage.cpp @@ -0,0 +1,431 @@ +#include "usermanage.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // 添加进程支持 +#include // 添加目录支持 + +// 初始化用户数据的实现 +void UserManager::initializeUsers() { + // 如果文件中有用户数据,就不初始化默认用户 + if (loadUsersFromFile()) { + qDebug() << "UserManager: 从文件加载用户数据成功"; + return; + } + + qDebug() << "UserManager: 初始化默认用户数据"; + + // 初始化默认测试用户(使用英文用户名避免编码问题) + users.emplace_back(L"zhangsan1", L"Abc123", Grade::PRIMARY); + users.emplace_back(L"zhangsan2", L"Abc123", Grade::PRIMARY); + users.emplace_back(L"zhangsan3", L"Abc123", Grade::PRIMARY); + + users.emplace_back(L"lisi1", L"Abc123", Grade::JUNIOR); + users.emplace_back(L"lisi2", L"Abc123", Grade::JUNIOR); + users.emplace_back(L"lisi3", L"Abc123", Grade::JUNIOR); + + users.emplace_back(L"wangwu1", L"Abc123", Grade::SENIOR); + users.emplace_back(L"wangwu2", L"Abc123", Grade::SENIOR); + users.emplace_back(L"wangwu3", L"Abc123", Grade::SENIOR); + + // 保存到文件 + if (saveUsersToFile()) { + qDebug() << "UserManager: 默认用户数据保存成功"; + } else { + qDebug() << "UserManager: 默认用户数据保存失败"; + } +} + +// 用户管理器的构造实现 +UserManager::UserManager() { + qDebug() << "UserManager: 构造函数调用"; + initializeUsers(); +} + +// 用户认证的实现 +User* UserManager::authenticateUser(const std::wstring& username, const std::wstring& password) { + qDebug() << "UserManager: 尝试认证用户:" << QString::fromStdWString(username); + + // 重新加载用户数据,确保包含新注册的用户 + loadUsersFromFile(); + + for (auto& user : users) { + if (user.check(username, password)) { + qDebug() << "UserManager: 用户认证成功"; + return &user; + } + } + + qDebug() << "UserManager: 用户认证失败"; + return nullptr; +} + +// 解析用户类型的实现 +Grade UserManager::parseUserType(const std::wstring& type) { + if (type == L"小学" || type == L"Primary") return Grade::PRIMARY; + if (type == L"初中" || type == L"Junior") return Grade::JUNIOR; + if (type == L"高中" || type == L"Senior") return Grade::SENIOR; + return Grade::PRIMARY; +} + +// 注册新用户的实现 +bool UserManager::registerUser(const std::wstring& username, const std::wstring& password, const std::wstring& grade) { + qDebug() << "UserManager: 尝试注册用户:" << QString::fromStdWString(username) << "年级:" << QString::fromStdWString(grade); + + // 重新加载用户数据,确保检查最新的用户名 + loadUsersFromFile(); + + // 检查用户名是否已存在 + if (isUsernameExists(username)) { + qDebug() << "UserManager: 用户名已存在"; + return false; + } + + Grade userGrade = parseUserType(grade); + users.emplace_back(username, password, userGrade); + + qDebug() << "UserManager: 用户添加到内存,准备保存到文件"; + + // 保存到文件 + bool result = saveUsersToFile(); + if (result) { + qDebug() << "UserManager: 用户注册成功"; + // 重新加载确保数据同步 + loadUsersFromFile(); + } else { + qDebug() << "UserManager: 用户注册失败 - 文件保存失败"; + } + + return result; +} + +// 修改用户密码的实现 +bool UserManager::changePassword(const std::wstring& username, const std::wstring& oldPassword, const std::wstring& newPassword) { + for (auto& user : users) { + if (user.check(username, oldPassword)) { + user.setPassword(newPassword); + return saveUsersToFile(); + } + } + return false; +} + +// 检查用户名是否存在的实现 +bool UserManager::isUsernameExists(const std::wstring& username) { + for (const auto& user : users) { + if (user.getUsername() == username) { + return true; + } + } + return false; +} + +// 从文件加载用户数据的实现 +bool UserManager::loadUsersFromFile() { + std::ifstream file(userFile); + if (!file.is_open()) { + qDebug() << "UserManager: 无法打开用户文件:" << userFile.c_str(); + return false; + } + + users.clear(); + std::string line; + int userCount = 0; + + while (std::getline(file, line)) { + std::istringstream iss(line); + std::string username, password, gradeStr; + + if (iss >> username >> password >> gradeStr) { + // 转换为宽字符串 + std::wstring wusername, wpassword, wgradeStr; + for (char c : username) wusername += wchar_t(c); + for (char c : password) wpassword += wchar_t(c); + for (char c : gradeStr) wgradeStr += wchar_t(c); + + Grade grade = parseUserType(wgradeStr); + users.emplace_back(wusername, wpassword, grade); + userCount++; + qDebug() << "加载用户:" << QString::fromStdWString(wusername) << "年级:" << QString::fromStdWString(wgradeStr); + } + } + + file.close(); + qDebug() << "UserManager: 从文件加载了" << userCount << "个用户"; + return !users.empty(); +} + +// 保存用户数据到文件的实现 +bool UserManager::saveUsersToFile() { + std::ofstream file(userFile); + if (!file.is_open()) { + qDebug() << "UserManager: 无法创建用户文件:" << userFile.c_str(); + return false; + } + + int savedCount = 0; + for (const auto& user : users) { + std::wstring username = user.getUsername(); + std::wstring password = user.getPassword(); + std::wstring gradeStr = user.getGradeString(); + + // 转换为普通字符串 + std::string username_str, password_str, grade_str; + for (wchar_t wc : username) username_str += static_cast(wc); + for (wchar_t wc : password) password_str += static_cast(wc); + for (wchar_t wc : gradeStr) grade_str += static_cast(wc); + + file << username_str << " " << password_str << " " << grade_str << "\n"; + savedCount++; + } + + file.close(); + qDebug() << "UserManager: 保存了" << savedCount << "个用户到文件"; + return true; +} + +// 生成验证码的实现 - 修复为每次都不同 +std::wstring UserManager::generateVerificationCode(int length) { + const std::wstring chars = L"0123456789"; + std::wstring code; + + // 使用当前时间作为随机种子,确保每次不同 + std::random_device rd; + std::mt19937 gen(rd() + static_cast(std::time(nullptr))); + std::uniform_int_distribution<> dis(0, chars.length() - 1); + + for (int i = 0; i < length; ++i) { + code += chars[dis(gen)]; + } + + qDebug() << "UserManager: 生成验证码:" << QString::fromStdWString(code); + return code; +} + +// 发送验证码的实现 - 添加真实邮件发送功能 +bool UserManager::sendVerificationCode(const std::wstring& email, std::wstring& generatedCode) { + qDebug() << "UserManager: 发送验证码到:" << QString::fromStdWString(email); + + // 生成验证码 + generatedCode = generateVerificationCode(); + + // 设置过期时间(10分钟) + auto expiryTime = std::chrono::system_clock::now() + std::chrono::minutes(10); + + // 存储验证码信息 + emailVerifications[email] = {generatedCode, expiryTime, false}; + + qDebug() << "UserManager: 验证码已存储,有效期10分钟"; + + // 尝试发送真实邮件 + if (sendRealEmail(email, generatedCode)) { + qDebug() << "UserManager: 验证码邮件发送成功"; + return true; + } else { + // 如果真实邮件发送失败,使用控制台输出 + std::wcout << L"[邮件模拟] 发送到: " << email << L" 的验证码: " << generatedCode << std::endl; + qDebug() << "UserManager: 真实邮件发送失败,使用模拟模式"; + + // 在调试窗口显示验证码信息 + QString qEmail = QString::fromStdWString(email); + QString qCode = QString::fromStdWString(generatedCode); + qDebug() << "=== 邮件发送模拟 ==="; + qDebug() << "收件人:" << qEmail; + qDebug() << "验证码:" << qCode; + qDebug() << "=== 邮件发送模拟 ==="; + + return true; + } +} + +// 验证邮箱验证码的实现 +bool UserManager::verifyEmailCode(const std::wstring& email, const std::wstring& code) { + qDebug() << "UserManager: 验证邮箱验证码,邮箱:" << QString::fromStdWString(email) + << "验证码:" << QString::fromStdWString(code); + + auto it = emailVerifications.find(email); + if (it == emailVerifications.end()) { + qDebug() << "UserManager: 没有找到该邮箱的验证码记录"; + return false; // 没有找到该邮箱的验证码记录 + } + + EmailVerification& verification = it->second; + + // 检查验证码是否已使用 + if (verification.used) { + qDebug() << "UserManager: 验证码已被使用"; + return false; + } + + // 检查验证码是否过期 + if (std::chrono::system_clock::now() > verification.expiryTime) { + qDebug() << "UserManager: 验证码已过期"; + emailVerifications.erase(it); + return false; + } + + // 检查验证码是否匹配 + if (verification.code == code) { + qDebug() << "UserManager: 验证码验证成功"; + verification.used = true; // 标记为已使用 + return true; + } + + qDebug() << "UserManager: 验证码不匹配"; + return false; +} + +// 真实邮件发送功能 - 仅使用 PowerShell +bool UserManager::sendRealEmail(const std::wstring& email, const std::wstring& code) { + QString qEmail = QString::fromStdWString(email); + QString qCode = QString::fromStdWString(code); + + qDebug() << "尝试使用 PowerShell 发送邮件到:" << qEmail; + + return sendEmailViaPowerShell(qEmail, qCode); +} + +// 辅助函数:安全地转义字符串中的单引号 +QString escapeSingleQuotes(const QString& input) { + QString result = input; + return result.replace("'", "''"); +} + +// 使用 PowerShell Send-MailMessage 发送邮件 +bool UserManager::sendEmailViaPowerShell(const QString& toEmail, const QString& code) { + qDebug() << "使用 PowerShell 发送邮件..."; + + // ==================== 配置区域 ==================== + // 请根据你的邮箱服务商修改以下配置: + + QString smtpServer = "smtp.qq.com"; // SMTP服务器 + int smtpPort = 587; // 端口号 + QString fromEmail = "1453386832@qq.com"; // ⬅️ 修改:发件人邮箱 + QString username = "1453386832@qq.com"; // ⬅️ 修改:邮箱账号 + QString password = "nijuqetihpojffag"; // ⬅️ 修改:邮箱授权码 + + /* + // 其他邮箱配置示例: + + // 163邮箱配置: + // QString smtpServer = "smtp.163.com"; + // int smtpPort = 25; // 或 587 + // QString fromEmail = "your_email@163.com"; + // QString username = "your_email@163.com"; + // QString password = "your_163_authorization_code"; + + // Gmail配置: + // QString smtpServer = "smtp.gmail.com"; + // int smtpPort = 587; + // QString fromEmail = "your_email@gmail.com"; + // QString username = "your_email@gmail.com"; + // QString password = "your_gmail_app_password"; + */ + // ==================== 配置结束 ==================== + + QString subject = "Math Learning Software - Verification Code"; + QString body = QString( + "Dear User:\n\n" + "You are registering for Math Learning Software. Your verification code is: %1\n\n" + "The verification code is valid for 10 minutes. Please complete your registration as soon as possible.\n\n" + "If this was not your operation, please ignore this email.\n\n" + "Math Learning Software Team" + ).arg(code); + + // 安全地转义所有字符串 + QString safePassword = escapeSingleQuotes(password); + QString safeUsername = escapeSingleQuotes(username); + QString safeFromEmail = escapeSingleQuotes(fromEmail); + QString safeToEmail = escapeSingleQuotes(toEmail); + QString safeSubject = escapeSingleQuotes(subject); + QString safeBody = escapeSingleQuotes(body); + QString safeSmtpServer = escapeSingleQuotes(smtpServer); + + // 构建 PowerShell 命令 + QString powerShellScript = QString( + "$secpasswd = ConvertTo-SecureString \"%1\" -AsPlainText -Force\n" + "$credential = New-Object System.Management.Automation.PSCredential(\"%2\", $secpasswd)\n" + "Send-MailMessage -From '%3' -To '%4' -Subject '%5' -Body '%6' -SmtpServer '%7' -Port %8 -Credential $credential -UseSsl" + ).arg(safePassword, + safeUsername, + safeFromEmail, + safeToEmail, + safeSubject, + safeBody, + safeSmtpServer, + QString::number(smtpPort)); + + // 创建临时 PowerShell 脚本文件 + QString tempScriptFile = QDir::tempPath() + "/send_email.ps1"; + QFile scriptFile(tempScriptFile); + + if (!scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + qDebug() << "无法创建临时 PowerShell 脚本文件"; + return false; + } + + QTextStream out(&scriptFile); + out << powerShellScript; + scriptFile.close(); + + qDebug() << "执行 PowerShell 脚本..."; + + // 执行 PowerShell 脚本 + QProcess process; + process.start("powershell", QStringList() << "-ExecutionPolicy" << "Bypass" << "-File" << tempScriptFile); + + if (!process.waitForStarted(10000)) { + qDebug() << "无法启动 PowerShell 进程"; + QFile::remove(tempScriptFile); + return false; + } + + if (!process.waitForFinished(60000)) { // 等待60秒 + qDebug() << "PowerShell 进程超时"; + process.kill(); + QFile::remove(tempScriptFile); + return false; + } + + int exitCode = process.exitCode(); + QByteArray output = process.readAllStandardOutput(); + QByteArray error = process.readAllStandardError(); + + qDebug() << "PowerShell 退出代码:" << exitCode; + + if (!output.isEmpty()) { + qDebug() << "PowerShell 输出:" << output; + } + + if (!error.isEmpty()) { + qDebug() << "PowerShell 错误:" << error; + } + + // 清理临时文件 + QFile::remove(tempScriptFile); + + if (exitCode == 0) { + qDebug() << "邮件发送成功!"; + return true; + } else { + qDebug() << "邮件发送失败,退出代码:" << exitCode; + + // 提供详细的错误信息 + if (error.contains("Authentication")) { + qDebug() << "错误:认证失败,请检查用户名和密码"; + } else if (error.contains("Connection")) { + qDebug() << "错误:连接失败,请检查SMTP服务器和端口"; + } else if (error.contains("SSL")) { + qDebug() << "错误:SSL连接失败"; + } + + return false; + } +} diff --git a/src/backend/usermanage.h b/src/backend/usermanage.h new file mode 100644 index 0000000..dac5933 --- /dev/null +++ b/src/backend/usermanage.h @@ -0,0 +1,75 @@ +#ifndef USERMANAGE_H +#define USERMANAGE_H + +#include "user.h" +#include +#include +#include +#include +#include +#include +#include +#include // 添加进程支持 + +// 邮箱验证码结构 +struct EmailVerification { + std::wstring code; + std::chrono::system_clock::time_point expiryTime; + bool used; +}; + +// 用户管理器类,负责用户数据的初始化和认证管理 +class UserManager { +private: + std::vector users; + const std::string userFile = "users.dat"; + + // 邮箱验证码存储 + std::map emailVerifications; + + //初始化预定义用户数据 + void initializeUsers(); + + //从文件加载用户数据 + bool loadUsersFromFile(); + + //保存用户数据到文件 + bool saveUsersToFile(); + + // 真实邮件发送 + bool sendRealEmail(const std::wstring& email, const std::wstring& code); + + // PowerShell 邮件发送方法 + bool sendEmailViaPowerShell(const QString& toEmail, const QString& code); + +public: + //构造函数,自动初始化用户数据 + UserManager(); + + //用户认证,验证用户名和密码 + User* authenticateUser(const std::wstring& username, const std::wstring& password); + + //将中文年级字符串解析为年级枚举值 + Grade parseUserType(const std::wstring& type); + + //注册新用户 + bool registerUser(const std::wstring& username, const std::wstring& password, const std::wstring& grade); + + //修改用户密码 + bool changePassword(const std::wstring& username, const std::wstring& oldPassword, const std::wstring& newPassword); + + //检查用户名是否存在 + bool isUsernameExists(const std::wstring& username); + + // 邮箱验证相关方法 + bool sendVerificationCode(const std::wstring& email, std::wstring& generatedCode); + bool verifyEmailCode(const std::wstring& email, const std::wstring& code); + + // 生成随机验证码 + std::wstring generateVerificationCode(int length = 6); + + //获取所有用户(用于测试) + std::vector& getUsers() { return users; } +}; + +#endif diff --git a/src/frontend/examwidget.cpp b/src/frontend/examwidget.cpp new file mode 100644 index 0000000..ab8d906 --- /dev/null +++ b/src/frontend/examwidget.cpp @@ -0,0 +1,376 @@ +#include "examwidget.h" +#include +#include +#include +#include +#include +#include +#include +#include + +ExamWidget::ExamWidget(QWidget *parent) : QWidget(parent), currentQuestion(0), totalQuestions(0) +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + + // 进度标签 + progressLabel = new QLabel(); + progressLabel->setAlignment(Qt::AlignCenter); + progressLabel->setStyleSheet("font-size: 16px; font-weight: bold; margin: 20px; color: #2c3e50;"); + + // 题目区域 + QGroupBox *questionGroup = new QGroupBox("题目"); + questionGroup->setStyleSheet("QGroupBox {" + "font-size: 16px;" + "font-weight: bold;" + "margin-top: 10px;" + "}" + "QGroupBox::title {" + "subcontrol-origin: margin;" + "subcontrol-position: top center;" + "padding: 0 5px;" + "}"); + + QVBoxLayout *questionLayout = new QVBoxLayout(questionGroup); + + questionLabel = new QLabel(); + questionLabel->setWordWrap(true); + questionLabel->setStyleSheet("font-size: 18px; margin: 20px; padding: 10px; background-color: #f8f9fa; border-radius: 5px;"); + questionLabel->setAlignment(Qt::AlignCenter); + questionLabel->setMinimumHeight(80); + + questionLayout->addWidget(questionLabel); + + // 选项区域 + QGroupBox *optionsGroup = new QGroupBox("请选择答案"); + optionsGroup->setStyleSheet("QGroupBox {" + "font-size: 16px;" + "font-weight: bold;" + "margin-top: 10px;" + "}" + "QGroupBox::title {" + "subcontrol-origin: margin;" + "subcontrol-position: top center;" + "padding: 0 5px;" + "}"); + + QVBoxLayout *optionsLayout = new QVBoxLayout(optionsGroup); + optionGroup = new QButtonGroup(this); + + for (int i = 0; i < 4; ++i) { + optionButtons[i] = new QRadioButton(); + optionButtons[i]->setStyleSheet("QRadioButton {" + "font-size: 14px;" + "margin: 8px;" + "padding: 8px;" + "}" + "QRadioButton::indicator {" + "width: 20px;" + "height: 20px;" + "}"); + optionGroup->addButton(optionButtons[i], i); + optionsLayout->addWidget(optionButtons[i]); + } + + // 按钮区域 + QWidget *buttonWidget = new QWidget(); + QHBoxLayout *buttonLayout = new QHBoxLayout(buttonWidget); + + // 上一题按钮 + previousButton = new QPushButton("上一题"); + previousButton->setStyleSheet("QPushButton {" + "background-color: #95a5a6;" + "color: white;" + "border: none;" + "padding: 10px 20px;" + "font-size: 14px;" + "border-radius: 5px;" + "}" + "QPushButton:hover {" + "background-color: #7f8c8d;" + "}" + "QPushButton:disabled {" + "background-color: #bdc3c7;" + "color: #7f8c8d;" + "}"); + previousButton->setFixedWidth(120); + previousButton->setEnabled(false); // 第一题时禁用 + + nextButton = new QPushButton("下一题"); + nextButton->setStyleSheet("QPushButton {" + "background-color: #3498db;" + "color: white;" + "border: none;" + "padding: 10px 20px;" + "font-size: 14px;" + "border-radius: 5px;" + "}" + "QPushButton:hover {" + "background-color: #2980b9;" + "}"); + nextButton->setFixedWidth(120); + + submitButton = new QPushButton("提交试卷"); + submitButton->setStyleSheet("QPushButton {" + "background-color: #e74c3c;" + "color: white;" + "border: none;" + "padding: 10px 20px;" + "font-size: 14px;" + "border-radius: 5px;" + "}" + "QPushButton:hover {" + "background-color: #c0392b;" + "}"); + submitButton->setFixedWidth(120); + submitButton->setVisible(false); + + buttonLayout->addStretch(); + buttonLayout->addWidget(previousButton); + buttonLayout->addSpacing(10); + buttonLayout->addWidget(nextButton); + buttonLayout->addSpacing(10); + buttonLayout->addWidget(submitButton); + buttonLayout->addStretch(); + + // 添加到主布局 + mainLayout->addWidget(progressLabel); + mainLayout->addWidget(questionGroup); + mainLayout->addWidget(optionsGroup); + mainLayout->addSpacing(20); + mainLayout->addWidget(buttonWidget); + mainLayout->addStretch(); + + // 连接信号槽 + connect(previousButton, &QPushButton::clicked, this, &ExamWidget::onPreviousClicked); + connect(nextButton, &QPushButton::clicked, this, &ExamWidget::onNextClicked); + connect(submitButton, &QPushButton::clicked, this, &ExamWidget::onSubmitClicked); +} + +void ExamWidget::startExam(Grade grade, int questionCount) +{ + try { + qDebug() << "ExamWidget: 开始考试,年级:" << static_cast(grade) << "题目数量:" << questionCount; + + questionGenerator.setGrade(grade); + questions = questionGenerator.generateQuestions(questionCount); + totalQuestions = static_cast(questions.size()); + currentQuestion = 0; + userAnswers.clear(); + correctAnswers.clear(); + questionOptions.clear(); + + if (questions.empty()) { + qDebug() << "ExamWidget: 题目生成失败"; + QMessageBox::warning(this, "错误", "题目生成失败,请重试"); + return; + } + + qDebug() << "ExamWidget: 成功生成" << totalQuestions << "个题目"; + + // 预计算所有题目的正确答案和选项 + for (int i = 0; i < totalQuestions; ++i) { + try { + double correctValue = questionGenerator.calculateCorrectAnswer(questions[i]); + + // 生成选项并保存 + std::vector options = questionGenerator.generateMeaningfulOptions(correctValue); + questionOptions.push_back(options); + + // 找到正确答案的索引 + int correctIndex = 0; + bool found = false; + + for (size_t j = 0; j < options.size(); ++j) { + try { + double optionValue = std::stod(options[j]); + if (std::abs(optionValue - correctValue) < 0.0001) { + correctIndex = static_cast(j); + found = true; + break; + } + } catch (...) { + // 如果转换失败,使用字符串比较 + std::stringstream oss; + oss << std::fixed << std::setprecision(6) << correctValue; + std::string correctStr = oss.str(); + std::wstring wcorrectStr(correctStr.begin(), correctStr.end()); + + if (options[j] == wcorrectStr) { + correctIndex = static_cast(j); + found = true; + break; + } + } + } + + if (!found) { + qDebug() << "警告:未找到第" << i << "题的正确答案在选项中"; + } + + correctAnswers.push_back(correctIndex); + qDebug() << "ExamWidget: 第" << i << "题正确答案:" << correctValue << "索引:" << correctIndex; + + } catch (const std::exception& e) { + qDebug() << "ExamWidget: 计算第" << i << "题答案异常:" << e.what(); + correctAnswers.push_back(0); // 默认第一个选项为正确答案 + questionOptions.push_back({L"1.000000", L"2.000000", L"3.000000", L"4.000000"}); + } + } + + showQuestion(0); + updateButtonStates(); // 更新按钮状态 + qDebug() << "ExamWidget: 考试初始化完成"; + } catch (const std::exception& e) { + qDebug() << "ExamWidget: 开始考试异常:" << e.what(); + QMessageBox::critical(this, "错误", QString("考试初始化失败: %1").arg(e.what())); + } +} + +void ExamWidget::showQuestion(int index) +{ + if (index < totalQuestions) { + currentQuestion = index; + progressLabel->setText(QString("第 %1 题 / 共 %2 题").arg(index + 1).arg(totalQuestions)); + + // 显示题目 + QString questionText = QString::fromStdWString(questions[index]); + questionLabel->setText(questionText); + + // 设置选项文本 - 使用预先生成的选项 + if (static_cast(index) < questionOptions.size()) { + for (int i = 0; i < 4; ++i) { + if (i < static_cast(questionOptions[index].size())) { + optionButtons[i]->setText(QString::fromStdWString(questionOptions[index][i])); + } else { + optionButtons[i]->setText(QString("选项 %1").arg(i + 1)); + } + } + } + + // 清除选择 + optionGroup->setExclusive(false); + for (int i = 0; i < 4; ++i) { + optionButtons[i]->setChecked(false); + } + optionGroup->setExclusive(true); + + // 恢复之前的选择(如果有) + if (static_cast(index) < userAnswers.size()) { + int previousAnswer = userAnswers[index]; + if (previousAnswer >= 0 && previousAnswer < 4) { + optionButtons[previousAnswer]->setChecked(true); + } + } + + // 更新按钮状态 + updateButtonStates(); + } +} + +void ExamWidget::updateButtonStates() +{ + // 更新上一题按钮状态 + previousButton->setEnabled(currentQuestion > 0); + + // 更新下一题和提交按钮状态 + nextButton->setVisible(currentQuestion < totalQuestions - 1); + submitButton->setVisible(currentQuestion == totalQuestions - 1); +} + +void ExamWidget::generateOptions() +{ + // 这个方法现在不再使用,因为选项在startExam中已经预生成 + // 保留这个方法是为了保持接口兼容性 +} + +void ExamWidget::onPreviousClicked() +{ + // 保存当前答案 + int selected = optionGroup->checkedId(); + if (selected != -1) { + if (static_cast(currentQuestion) >= userAnswers.size()) { + userAnswers.resize(currentQuestion + 1); + } + userAnswers[currentQuestion] = selected; + } + + // 显示上一题 + if (currentQuestion > 0) { + showQuestion(currentQuestion - 1); + } +} + +void ExamWidget::onNextClicked() +{ + int selected = optionGroup->checkedId(); + if (selected == -1) { + QMessageBox::warning(this, "提示", "请选择一个答案"); + return; + } + + // 保存当前答案 + if (static_cast(currentQuestion) >= userAnswers.size()) { + userAnswers.resize(currentQuestion + 1); + } + userAnswers[currentQuestion] = selected; + + showQuestion(currentQuestion + 1); +} + +void ExamWidget::onSubmitClicked() +{ + int selected = optionGroup->checkedId(); + if (selected == -1) { + QMessageBox::warning(this, "提示", "请选择一个答案"); + return; + } + + // 保存最后一题的答案 + if (static_cast(currentQuestion) >= userAnswers.size()) { + userAnswers.resize(currentQuestion + 1); + } + userAnswers[currentQuestion] = selected; + + // 计算分数 - 使用预先生成的选项进行比较 + int score = 0; + for (size_t i = 0; i < userAnswers.size(); ++i) { + if (i < correctAnswers.size() && i < questionOptions.size()) { + if (userAnswers[i] >= 0 && userAnswers[i] < static_cast(questionOptions[i].size())) { + std::wstring selectedOption = questionOptions[i][userAnswers[i]]; + + try { + double selectedValue = std::stod(selectedOption); + double correctValue = questionGenerator.calculateCorrectAnswer(questions[i]); + + qDebug() << "第" << i << "题 - 选择值:" << selectedValue + << "正确答案:" << correctValue + << "差值:" << std::abs(selectedValue - correctValue); + + if (std::abs(selectedValue - correctValue) < 1e-5) { + score++; + qDebug() << "第" << i << "题回答正确"; + } else { + qDebug() << "第" << i << "题回答错误,选择值:" << selectedValue + << "正确答案:" << correctValue; + } + } catch (const std::exception& e) { + qDebug() << "第" << i << "题数值转换异常:" << e.what(); + // 回退到索引比较 + if (userAnswers[i] == correctAnswers[i]) { + score++; + qDebug() << "第" << i << "题通过索引比较回答正确"; + } else { + qDebug() << "第" << i << "题通过索引比较回答错误"; + } + } + } else { + qDebug() << "第" << i << "题用户答案索引越界:" << userAnswers[i]; + } + } else { + qDebug() << "第" << i << "题数据不完整"; + } + } + + qDebug() << "ExamWidget: 考试完成,分数:" << score << "/" << totalQuestions; + emit examFinished(score, totalQuestions); +} diff --git a/src/frontend/examwidget.h b/src/frontend/examwidget.h new file mode 100644 index 0000000..e741829 --- /dev/null +++ b/src/frontend/examwidget.h @@ -0,0 +1,56 @@ +#ifndef EXAMWIDGET_H +#define EXAMWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "questiongenerator.h" +#include "user.h" + +class ExamWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ExamWidget(QWidget *parent = nullptr); + void startExam(Grade grade, int questionCount); + +signals: + void examFinished(int score, int total); + +private slots: + void onNextClicked(); + void onPreviousClicked(); // 上一题 + void onSubmitClicked(); + +private: + void showQuestion(int index); + void generateOptions(); + void updateButtonStates(); // 更新按钮状态 + + QLabel *questionLabel; + QLabel *progressLabel; + QButtonGroup *optionGroup; + QRadioButton *optionButtons[4]; + QPushButton *previousButton; // 上一题按钮 + QPushButton *nextButton; + QPushButton *submitButton; + + QuestionGenerator questionGenerator; + std::vector questions; + std::vector userAnswers; + std::vector correctAnswers; + std::vector> questionOptions; + + int currentQuestion; + int totalQuestions; +}; + +#endif diff --git a/src/frontend/loginwidget.cpp b/src/frontend/loginwidget.cpp new file mode 100644 index 0000000..6a1abb0 --- /dev/null +++ b/src/frontend/loginwidget.cpp @@ -0,0 +1,153 @@ +#include "loginwidget.h" +#include +#include +#include +#include +#include + +LoginWidget::LoginWidget(QWidget *parent) : QWidget(parent) +{ + // 设置最小尺寸 + setMinimumSize(400, 400); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setSpacing(20); + layout->setContentsMargins(50, 50, 50, 50); + + // 标题 + QLabel *titleLabel = new QLabel("数学学习软件 - 登录"); + titleLabel->setAlignment(Qt::AlignCenter); + titleLabel->setStyleSheet("font-size: 24px; font-weight: bold; margin: 20px; color: #2c3e50;"); + + // 用户名输入 + QLabel *usernameLabel = new QLabel("用户名:"); + usernameLabel->setStyleSheet("font-size: 14px; font-weight: bold;"); + usernameEdit = new QLineEdit(); + usernameEdit->setPlaceholderText("请输入用户名"); + usernameEdit->setMinimumHeight(35); + usernameEdit->setStyleSheet("QLineEdit { padding: 8px; font-size: 14px; border: 1px solid #bdc3c7; border-radius: 4px; }"); + + // 密码输入 + QLabel *passwordLabel = new QLabel("密码:"); + passwordLabel->setStyleSheet("font-size: 14px; font-weight: bold;"); + passwordEdit = new QLineEdit(); + passwordEdit->setEchoMode(QLineEdit::Password); + passwordEdit->setPlaceholderText("请输入密码"); + passwordEdit->setMinimumHeight(35); + passwordEdit->setStyleSheet("QLineEdit { padding: 8px; font-size: 14px; border: 1px solid #bdc3c7; border-radius: 4px; }"); + + // 按钮区域 + QWidget *buttonWidget = new QWidget(); + QVBoxLayout *buttonLayout = new QVBoxLayout(buttonWidget); + buttonLayout->setSpacing(15); + + loginButton = new QPushButton("登录"); + loginButton->setMinimumHeight(40); + loginButton->setStyleSheet("QPushButton {" + "background-color: #3498db;" + "color: white;" + "border: none;" + "font-size: 16px;" + "font-weight: bold;" + "border-radius: 5px;" + "}" + "QPushButton:hover {" + "background-color: #2980b9;" + "}"); + + registerButton = new QPushButton("注册新账号"); + registerButton->setMinimumHeight(40); + registerButton->setStyleSheet("QPushButton {" + "background-color: #95a5a6;" + "color: white;" + "border: none;" + "font-size: 16px;" + "font-weight: bold;" + "border-radius: 5px;" + "}" + "QPushButton:hover {" + "background-color: #7f8c8d;" + "}"); + + // 添加退出按钮 + QPushButton *exitButton = new QPushButton("退出系统"); + exitButton->setMinimumHeight(40); + exitButton->setStyleSheet("QPushButton {" + "background-color: #e74c3c;" + "color: white;" + "border: none;" + "font-size: 16px;" + "font-weight: bold;" + "border-radius: 5px;" + "}" + "QPushButton:hover {" + "background-color: #c0392b;" + "}"); + + // 测试账号提示 + QLabel *testAccountLabel = new QLabel("测试账号: zhangsan1 / lisi1 / wangwu1 密码: Abc123"); + testAccountLabel->setStyleSheet("font-size: 12px; color: #7f8c8d; margin-top: 10px;"); + testAccountLabel->setAlignment(Qt::AlignCenter); + + buttonLayout->addWidget(loginButton); + buttonLayout->addWidget(registerButton); + buttonLayout->addWidget(exitButton); + buttonLayout->addWidget(testAccountLabel); + + // 添加到布局 + layout->addWidget(titleLabel); + layout->addSpacing(30); + layout->addWidget(usernameLabel); + layout->addWidget(usernameEdit); + layout->addWidget(passwordLabel); + layout->addWidget(passwordEdit); + layout->addSpacing(30); + layout->addWidget(buttonWidget); + + // 连接信号槽 + connect(loginButton, &QPushButton::clicked, this, &LoginWidget::onLoginClicked); + connect(registerButton, &QPushButton::clicked, this, &LoginWidget::onRegisterClicked); + connect(exitButton, &QPushButton::clicked, qApp, &QApplication::quit); + + // 回车键登录 + connect(usernameEdit, &QLineEdit::returnPressed, this, &LoginWidget::onLoginClicked); + connect(passwordEdit, &QLineEdit::returnPressed, this, &LoginWidget::onLoginClicked); + + qDebug() << "LoginWidget: 初始化完成"; +} + +void LoginWidget::onLoginClicked() +{ + QString username = usernameEdit->text().trimmed(); + QString password = passwordEdit->text(); + + qDebug() << "LoginWidget: 尝试登录,用户名:" << username; + + if (username.isEmpty() || password.isEmpty()) { + QMessageBox::warning(this, "输入错误", "请输入用户名和密码"); + return; + } + + User* user = userManager.authenticateUser(username.toStdWString(), + password.toStdWString()); + if (user) { + qDebug() << "LoginWidget: 登录成功"; + emit loginSuccess(user); + } else { + qDebug() << "LoginWidget: 登录失败"; + QMessageBox::warning(this, "登录失败", "用户名或密码错误"); + } +} + +void LoginWidget::onRegisterClicked() +{ + qDebug() << "LoginWidget: 切换到注册界面"; + emit showRegister(); +} + +void LoginWidget::clearInputs() +{ + qDebug() << "LoginWidget: 清空输入框"; + usernameEdit->clear(); + passwordEdit->clear(); +} diff --git a/src/frontend/loginwidget.h b/src/frontend/loginwidget.h new file mode 100644 index 0000000..87fe657 --- /dev/null +++ b/src/frontend/loginwidget.h @@ -0,0 +1,38 @@ +#ifndef LOGINWIDGET_H +#define LOGINWIDGET_H + +#include +#include +#include +#include +#include +#include +#include "usermanage.h" + +class LoginWidget : public QWidget +{ + Q_OBJECT + +public: + explicit LoginWidget(QWidget *parent = nullptr); + +signals: + void loginSuccess(User* user); + void showRegister(); + +private slots: + void onLoginClicked(); + void onRegisterClicked(); + +public slots: + void clearInputs(); + +private: + QLineEdit *usernameEdit; // 改为用户名输入 + QLineEdit *passwordEdit; + QPushButton *loginButton; + QPushButton *registerButton; + UserManager userManager; +}; + +#endif diff --git a/src/frontend/main.cpp b/src/frontend/main.cpp new file mode 100644 index 0000000..e1366fd --- /dev/null +++ b/src/frontend/main.cpp @@ -0,0 +1,24 @@ +#include +#include +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + qDebug() << "应用程序启动..."; + + // 设置应用程序信息 + app.setApplicationName("数学学习软件"); + app.setApplicationVersion("1.0"); + app.setOrganizationName("软件工程学院"); + + qDebug() << "创建主窗口..."; + MainWindow window; + + qDebug() << "显示主窗口..."; + window.show(); + + qDebug() << "进入事件循环..."; + return app.exec(); +} diff --git a/src/frontend/mainmenuwidget.cpp b/src/frontend/mainmenuwidget.cpp new file mode 100644 index 0000000..ae6f2a1 --- /dev/null +++ b/src/frontend/mainmenuwidget.cpp @@ -0,0 +1,182 @@ +#include "mainmenuwidget.h" +#include +#include + +MainMenuWidget::MainMenuWidget(QWidget *parent) : QWidget(parent) +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + + // 标题和欢迎信息 + QLabel *titleLabel = new QLabel("数学学习软件"); + titleLabel->setAlignment(Qt::AlignCenter); + titleLabel->setStyleSheet("font-size: 24px; font-weight: bold; margin: 30px; color: #2c3e50;"); + + welcomeLabel = new QLabel("欢迎使用数学学习软件"); + welcomeLabel->setAlignment(Qt::AlignCenter); + welcomeLabel->setStyleSheet("font-size: 18px; margin: 20px; color: #34495e;"); + + userInfoLabel = new QLabel(); + userInfoLabel->setAlignment(Qt::AlignCenter); + userInfoLabel->setStyleSheet("font-size: 14px; margin: 10px; color: #7f8c8d;"); + + // 设置区域 + QGroupBox *settingsGroup = new QGroupBox("考试设置"); + settingsGroup->setStyleSheet("QGroupBox {" + "font-size: 16px;" + "font-weight: bold;" + "margin-top: 10px;" + "}" + "QGroupBox::title {" + "subcontrol-origin: margin;" + "subcontrol-position: top center;" + "padding: 0 5px;" + "}"); + + QVBoxLayout *settingsLayout = new QVBoxLayout(settingsGroup); + + // 年级选择 + QWidget *gradeWidget = new QWidget(); + QHBoxLayout *gradeLayout = new QHBoxLayout(gradeWidget); + QLabel *gradeLabel = new QLabel("选择年级:"); + gradeLabel->setStyleSheet("font-size: 14px;"); + gradeComboBox = new QComboBox(); + gradeComboBox->addItem("小学"); + gradeComboBox->addItem("初中"); + gradeComboBox->addItem("高中"); + gradeComboBox->setStyleSheet("padding: 8px; font-size: 14px;"); + gradeLayout->addWidget(gradeLabel); + gradeLayout->addWidget(gradeComboBox); + gradeLayout->addStretch(); + + // 题目数量 + QWidget *countWidget = new QWidget(); + QHBoxLayout *countLayout = new QHBoxLayout(countWidget); + QLabel *countLabel = new QLabel("题目数量:"); + countLabel->setStyleSheet("font-size: 14px;"); + questionCountSpinBox = new QSpinBox(); + questionCountSpinBox->setRange(5, 30); + questionCountSpinBox->setValue(10); + questionCountSpinBox->setStyleSheet("padding: 8px; font-size: 14px;"); + countLayout->addWidget(countLabel); + countLayout->addWidget(questionCountSpinBox); + countLayout->addStretch(); + + // 开始按钮 + startButton = new QPushButton("开始考试"); + startButton->setStyleSheet("QPushButton {" + "background-color: #e74c3c;" + "color: white;" + "border: none;" + "padding: 12px 30px;" + "font-size: 16px;" + "border-radius: 5px;" + "}" + "QPushButton:hover {" + "background-color: #c0392b;" + "}"); + startButton->setFixedSize(150, 50); + + // 退出登录按钮 + logoutButton = new QPushButton("退出登录"); + logoutButton->setStyleSheet("QPushButton {" + "background-color: #95a5a6;" + "color: white;" + "border: none;" + "padding: 12px 30px;" + "font-size: 16px;" + "border-radius: 5px;" + "}" + "QPushButton:hover {" + "background-color: #7f8c8d;" + "}"); + logoutButton->setFixedSize(150, 50); // 修改为相同大小 + + // 按钮容器 - 重新设计布局 + QWidget *buttonWidget = new QWidget(); + QVBoxLayout *buttonLayout = new QVBoxLayout(buttonWidget); + buttonLayout->setSpacing(15); // 设置按钮间距 + buttonLayout->setContentsMargins(0, 20, 0, 10); + + // 开始考试按钮行 + QWidget *startButtonWidget = new QWidget(); + QHBoxLayout *startButtonLayout = new QHBoxLayout(startButtonWidget); + startButtonLayout->addStretch(); + startButtonLayout->addWidget(startButton); + startButtonLayout->addStretch(); + + // 退出登录按钮行 + QWidget *logoutButtonWidget = new QWidget(); + QHBoxLayout *logoutButtonLayout = new QHBoxLayout(logoutButtonWidget); + logoutButtonLayout->addStretch(); + logoutButtonLayout->addWidget(logoutButton); + logoutButtonLayout->addStretch(); + + // 添加到按钮布局 + buttonLayout->addWidget(startButtonWidget); + buttonLayout->addWidget(logoutButtonWidget); + + // 添加到设置布局 + settingsLayout->addWidget(gradeWidget); + settingsLayout->addWidget(countWidget); + settingsLayout->addSpacing(20); + settingsLayout->addWidget(buttonWidget); + + // 添加到主布局 + mainLayout->addWidget(titleLabel); + mainLayout->addWidget(welcomeLabel); + mainLayout->addWidget(userInfoLabel); + mainLayout->addSpacing(20); + mainLayout->addWidget(settingsGroup); + mainLayout->addStretch(); + + // 连接信号槽 + connect(startButton, &QPushButton::clicked, this, &MainMenuWidget::onStartExamClicked); + connect(logoutButton, &QPushButton::clicked, this, &MainMenuWidget::onLogoutClicked); + + qDebug() << "MainMenuWidget: 初始化完成,退出登录按钮大小已调整"; +} + +void MainMenuWidget::setUserInfo(const std::wstring& username, const std::wstring& grade) +{ + QString userInfo = QString("当前用户: %1 | 注册年级: %2") + .arg(QString::fromStdWString(username)) + .arg(QString::fromStdWString(grade)); + userInfoLabel->setText(userInfo); + + // 设置年级选择框默认值 + QString gradeStr = QString::fromStdWString(grade); + int index = gradeComboBox->findText(gradeStr); + if (index >= 0) { + gradeComboBox->setCurrentIndex(index); + } +} + +QString MainMenuWidget::getSelectedGrade() const { + return gradeComboBox->currentText(); +} + +void MainMenuWidget::onStartExamClicked() +{ + int questionCount = questionCountSpinBox->value(); + QString selectedGrade = gradeComboBox->currentText(); + qDebug() << "MainMenuWidget: 开始考试,题目数量:" << questionCount << "选择年级:" << selectedGrade; + emit startExam(questionCount); +} + +void MainMenuWidget::onLogoutClicked() +{ + qDebug() << "MainMenuWidget: 用户请求退出登录"; + + // 弹出确认对话框 + QMessageBox::StandardButton reply; + reply = QMessageBox::question(this, "确认退出", + "确定要退出登录吗?", + QMessageBox::Yes | QMessageBox::No); + + if (reply == QMessageBox::Yes) { + qDebug() << "MainMenuWidget: 用户确认退出登录"; + emit logoutRequested(); + } else { + qDebug() << "MainMenuWidget: 用户取消退出登录"; + } +} diff --git a/src/frontend/mainmenuwidget.h b/src/frontend/mainmenuwidget.h new file mode 100644 index 0000000..f3c56d4 --- /dev/null +++ b/src/frontend/mainmenuwidget.h @@ -0,0 +1,39 @@ +#ifndef MAINMENUWIDGET_H +#define MAINMENUWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include + +class MainMenuWidget : public QWidget +{ + Q_OBJECT + +public: + explicit MainMenuWidget(QWidget *parent = nullptr); + void setUserInfo(const std::wstring& username, const std::wstring& grade); + QString getSelectedGrade() const; + +signals: + void startExam(int questionCount); + void logoutRequested(); // 退出登录 + +private slots: + void onStartExamClicked(); + void onLogoutClicked(); // 处理退出登录 + +private: + QLabel *welcomeLabel; + QLabel *userInfoLabel; + QComboBox *gradeComboBox; + QSpinBox *questionCountSpinBox; + QPushButton *startButton; + QPushButton *logoutButton; // 退出登录按钮 +}; + +#endif diff --git a/src/frontend/mainwindow.cpp b/src/frontend/mainwindow.cpp new file mode 100644 index 0000000..f3064ad --- /dev/null +++ b/src/frontend/mainwindow.cpp @@ -0,0 +1,203 @@ +#include "mainwindow.h" +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), currentUser(nullptr) +{ + setWindowTitle("中小学数学学习软件"); + setMinimumSize(600, 700); + resize(600, 700); + + qDebug() << "MainWindow: 初始化开始"; + + // 创建堆叠窗口 + stackedWidget = new QStackedWidget(this); + setCentralWidget(stackedWidget); + + // 创建各个界面 + loginWidget = new LoginWidget(); + registerWidget = new RegisterWidget(); + mainMenuWidget = new MainMenuWidget(); + examWidget = new ExamWidget(); + resultWidget = new ResultWidget(); + + qDebug() << "MainWindow: 所有界面创建完成"; + + // 添加到堆叠窗口 + stackedWidget->addWidget(loginWidget); + stackedWidget->addWidget(registerWidget); + stackedWidget->addWidget(mainMenuWidget); + stackedWidget->addWidget(examWidget); + stackedWidget->addWidget(resultWidget); + + // 连接信号槽 + connect(loginWidget, &LoginWidget::loginSuccess, this, &MainWindow::onUserLoggedIn); + connect(loginWidget, &LoginWidget::showRegister, this, &MainWindow::showRegister); + + connect(registerWidget, &RegisterWidget::showLogin, this, &MainWindow::showLogin); + connect(registerWidget, &RegisterWidget::registerSuccess, this, &MainWindow::onRegisterSuccess); + + connect(mainMenuWidget, &MainMenuWidget::startExam, this, &MainWindow::showExam); + connect(mainMenuWidget, &MainMenuWidget::logoutRequested, this, &MainWindow::onLogoutRequested); // 新增连接 + + connect(examWidget, &ExamWidget::examFinished, this, &MainWindow::showResult); + connect(resultWidget, &ResultWidget::backToMenu, this, &MainWindow::showMainMenu); + connect(resultWidget, &ResultWidget::startNewExam, this, &MainWindow::showMainMenu); + + qDebug() << "MainWindow: 信号连接完成"; + + // 显示登录界面 + showLogin(); +} + +MainWindow::~MainWindow() +{ + qDebug() << "MainWindow: 析构函数调用"; +} + +void MainWindow::showLogin() +{ + qDebug() << "MainWindow: 切换到登录界面"; + stackedWidget->setCurrentWidget(loginWidget); + setWindowTitle("中小学数学学习软件 - 登录"); +} + +void MainWindow::showRegister() +{ + qDebug() << "MainWindow: 切换到注册界面"; + stackedWidget->setCurrentWidget(registerWidget); + setWindowTitle("中小学数学学习软件 - 注册"); +} + +void MainWindow::showMainMenu() +{ + qDebug() << "MainWindow: 切换到主菜单"; + if (currentUser) { + mainMenuWidget->setUserInfo(currentUser->getUsername(), + currentUser->getGradeString()); + stackedWidget->setCurrentWidget(mainMenuWidget); + setWindowTitle("中小学数学学习软件 - 主菜单"); + } else { + qDebug() << "MainWindow: 当前用户为空,跳转到登录界面"; + QMessageBox::warning(this, "会话过期", "用户会话已过期,请重新登录"); + showLogin(); + } +} + +void MainWindow::showExam(int questionCount) +{ + qDebug() << "MainWindow: 开始考试,题目数量:" << questionCount; + if (currentUser) { + // 从主菜单获取当前选择的年级,而不是用户注册时的年级 + QString selectedGrade = mainMenuWidget->getSelectedGrade(); + qDebug() << "MainWindow: 用户注册年级:" << QString::fromStdWString(currentUser->getGradeString()) + << "选择的年级:" << selectedGrade; + + // 根据选择的年级设置考试难度 + Grade examGrade = Grade::PRIMARY; // 默认小学 + + if (selectedGrade == "小学") { + examGrade = Grade::PRIMARY; + } else if (selectedGrade == "初中") { + examGrade = Grade::JUNIOR; + } else if (selectedGrade == "高中") { + examGrade = Grade::SENIOR; + } + + qDebug() << "MainWindow: 实际考试年级:" << static_cast(examGrade); + examWidget->startExam(examGrade, questionCount); + stackedWidget->setCurrentWidget(examWidget); + setWindowTitle("中小学数学学习软件 - 考试中"); + } else { + qDebug() << "MainWindow: 考试时用户未登录"; + QMessageBox::warning(this, "错误", "用户未登录,请重新登录"); + showLogin(); + } +} + +void MainWindow::showResult(int score, int total) +{ + qDebug() << "MainWindow: 显示考试结果,分数:" << score << "/" << total; + resultWidget->setResult(score, total); + stackedWidget->setCurrentWidget(resultWidget); + setWindowTitle("中小学数学学习软件 - 考试结果"); + + // 保存考试记录 + if (currentUser) { + bool saved = FileSaver::saveExamRecord(currentUser->getUsername(), + currentUser->getGradeString(), + score, total, + FileSaver::getCurrentTime()); + if (saved) { + qDebug() << "MainWindow: 考试记录保存成功"; + } else { + qDebug() << "MainWindow: 考试记录保存失败"; + } + } +} + +void MainWindow::onUserLoggedIn(User* user) +{ + qDebug() << "MainWindow: 用户登录成功"; + if (user) { + currentUser = user; + QString username = QString::fromStdWString(user->getUsername()); + QString grade = QString::fromStdWString(user->getGradeString()); + + qDebug() << "MainWindow: 登录用户:" << username << "年级:" << grade; + + QMessageBox::information(this, "登录成功", + QString("欢迎 %1!\n年级:%2") + .arg(username) + .arg(grade)); + showMainMenu(); + } else { + qDebug() << "MainWindow: 用户信息无效"; + QMessageBox::warning(this, "登录失败", "用户信息无效"); + showLogin(); + } +} + +void MainWindow::onRegisterSuccess() +{ + qDebug() << "MainWindow: 注册成功,准备跳转到登录界面"; + + // 注册成功后显示登录界面,并显示成功提示 + QMessageBox::information(this, "注册成功", "注册成功!请使用新账号登录"); + + // 清空登录界面的输入框 + if (loginWidget) { + loginWidget->clearInputs(); + } + + qDebug() << "MainWindow: 清空登录界面输入框完成"; + + // 立即切换到登录界面 + showLogin(); + + qDebug() << "MainWindow: 已切换到登录界面"; +} + +void MainWindow::onLogoutRequested() +{ + qDebug() << "MainWindow: 处理退出登录请求"; + + // 清除当前用户信息 + if (currentUser) { + QString username = QString::fromStdWString(currentUser->getUsername()); + qDebug() << "MainWindow: 用户" << username << "退出登录"; + currentUser = nullptr; + } + + // 清空登录界面的输入框 + if (loginWidget) { + loginWidget->clearInputs(); + } + + // 显示登录界面 + showLogin(); + + QMessageBox::information(this, "退出成功", "您已成功退出登录"); +} diff --git a/src/frontend/mainwindow.h b/src/frontend/mainwindow.h new file mode 100644 index 0000000..7135fcc --- /dev/null +++ b/src/frontend/mainwindow.h @@ -0,0 +1,43 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include "loginwidget.h" +#include "registerwidget.h" +#include "mainmenuwidget.h" +#include "examwidget.h" +#include "resultwidget.h" +#include "usermanage.h" +#include "filesaver.h" + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + void showLogin(); + void showRegister(); + void showMainMenu(); + void showExam(int questionCount); + void showResult(int score, int total); + void onUserLoggedIn(User* user); + void onRegisterSuccess(); + void onLogoutRequested(); // 处理退出登录 + +private: + QStackedWidget *stackedWidget; + LoginWidget *loginWidget; + RegisterWidget *registerWidget; + MainMenuWidget *mainMenuWidget; + ExamWidget *examWidget; + ResultWidget *resultWidget; + UserManager userManager; + User *currentUser; +}; + +#endif diff --git a/src/frontend/mainwindow.ui b/src/frontend/mainwindow.ui new file mode 100644 index 0000000..b232854 --- /dev/null +++ b/src/frontend/mainwindow.ui @@ -0,0 +1,22 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + + diff --git a/src/frontend/mathlearningApp.pro b/src/frontend/mathlearningApp.pro new file mode 100644 index 0000000..dd44b16 --- /dev/null +++ b/src/frontend/mathlearningApp.pro @@ -0,0 +1,49 @@ +QT += core gui network + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + filesaver.cpp \ + questiongenerator.cpp \ + user.cpp \ + usermanage.cpp \ + loginwidget.cpp \ + main.cpp \ + mainmenuwidget.cpp \ + mainwindow.cpp \ + registerwidget.cpp \ + resultwidget.cpp \ + examwidget.cpp + +HEADERS += \ + filesaver.h \ + questiongenerator.h \ + user.h \ + usermanage.h \ + loginwidget.h \ + mainmenuwidget.h \ + mainwindow.h \ + registerwidget.h \ + resultwidget.h \ + examwidget.h + +FORMS += \ + mainwindow.ui + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/src/frontend/registerwidget.cpp b/src/frontend/registerwidget.cpp new file mode 100644 index 0000000..1e20d93 --- /dev/null +++ b/src/frontend/registerwidget.cpp @@ -0,0 +1,391 @@ +#include "registerwidget.h" +#include "usermanage.h" +#include +#include +#include +#include +#include +#include + +RegisterWidget::RegisterWidget(QWidget *parent) : QWidget(parent), countdownSeconds(0) +{ + // 设置最小尺寸 + setMinimumSize(500, 600); + + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setSpacing(20); + mainLayout->setContentsMargins(40, 30, 40, 30); + + // 标题 + QLabel *titleLabel = new QLabel("用户注册"); + titleLabel->setAlignment(Qt::AlignCenter); + titleLabel->setStyleSheet("font-size: 28px; font-weight: bold; margin: 20px; color: #2c3e50;"); + + // 创建网格布局用于表单 + QGridLayout *formLayout = new QGridLayout(); + formLayout->setSpacing(15); + formLayout->setColumnMinimumWidth(0, 100); + formLayout->setColumnStretch(1, 1); + + // 邮箱输入 + QLabel *emailLabel = new QLabel("邮箱:"); + emailLabel->setStyleSheet("font-size: 14px; font-weight: bold;"); + emailEdit = new QLineEdit(); + emailEdit->setPlaceholderText("请输入您的邮箱"); + emailEdit->setMinimumHeight(35); + emailEdit->setStyleSheet("QLineEdit { padding: 8px; font-size: 14px; border: 1px solid #bdc3c7; border-radius: 4px; }"); + + // 发送验证码按钮 + sendCodeButton = new QPushButton("发送验证码"); + sendCodeButton->setFixedWidth(120); + sendCodeButton->setMinimumHeight(35); + sendCodeButton->setStyleSheet("QPushButton {" + "background-color: #3498db;" + "color: white;" + "border: none;" + "font-size: 12px;" + "border-radius: 4px;" + "}" + "QPushButton:hover {" + "background-color: #2980b9;" + "}" + "QPushButton:disabled {" + "background-color: #bdc3c7;" + "}"); + + // 验证码输入 + QLabel *codeLabel = new QLabel("验证码:"); + codeLabel->setStyleSheet("font-size: 14px; font-weight: bold;"); + verificationCodeEdit = new QLineEdit(); + verificationCodeEdit->setPlaceholderText("请输入收到的验证码"); + verificationCodeEdit->setMinimumHeight(35); + verificationCodeEdit->setStyleSheet("QLineEdit { padding: 8px; font-size: 14px; border: 1px solid #bdc3c7; border-radius: 4px; }"); + + // 计时器标签 + timerLabel = new QLabel(); + timerLabel->setStyleSheet("color: #e74c3c; font-size: 14px; font-weight: bold;"); + timerLabel->setFixedWidth(60); + timerLabel->setAlignment(Qt::AlignCenter); + + // 用户名输入 + QLabel *usernameLabel = new QLabel("用户名:"); + usernameLabel->setStyleSheet("font-size: 14px; font-weight: bold;"); + usernameEdit = new QLineEdit(); + usernameEdit->setPlaceholderText("请输入用户名(2-20个字符,建议使用英文)"); + usernameEdit->setMinimumHeight(35); + usernameEdit->setStyleSheet("QLineEdit { padding: 8px; font-size: 14px; border: 1px solid #bdc3c7; border-radius: 4px; }"); + + // 年级选择 + QLabel *gradeLabel = new QLabel("选择年级:"); + gradeLabel->setStyleSheet("font-size: 14px; font-weight: bold;"); + gradeComboBox = new QComboBox(); + gradeComboBox->addItem("小学"); + gradeComboBox->addItem("初中"); + gradeComboBox->addItem("高中"); + gradeComboBox->setMinimumHeight(35); + gradeComboBox->setStyleSheet("QComboBox { padding: 8px; font-size: 14px; border: 1px solid #bdc3c7; border-radius: 4px; }"); + + // 密码输入 + QLabel *passwordLabel = new QLabel("密码:"); + passwordLabel->setStyleSheet("font-size: 14px; font-weight: bold;"); + passwordEdit = new QLineEdit(); + passwordEdit->setEchoMode(QLineEdit::Password); + passwordEdit->setPlaceholderText("6-10位,包含大小写字母和数字"); + passwordEdit->setMinimumHeight(35); + passwordEdit->setStyleSheet("QLineEdit { padding: 8px; font-size: 14px; border: 1px solid #bdc3c7; border-radius: 4px; }"); + + // 确认密码 + QLabel *confirmPasswordLabel = new QLabel("确认密码:"); + confirmPasswordLabel->setStyleSheet("font-size: 14px; font-weight: bold;"); + confirmPasswordEdit = new QLineEdit(); + confirmPasswordEdit->setEchoMode(QLineEdit::Password); + confirmPasswordEdit->setPlaceholderText("请再次输入密码"); + confirmPasswordEdit->setMinimumHeight(35); + confirmPasswordEdit->setStyleSheet("QLineEdit { padding: 8px; font-size: 14px; border: 1px solid #bdc3c7; border-radius: 4px; }"); + + // 设置表单布局 + int row = 0; + + // 邮箱行 + formLayout->addWidget(emailLabel, row, 0, Qt::AlignRight | Qt::AlignVCenter); + QHBoxLayout *emailLayout = new QHBoxLayout(); + emailLayout->setSpacing(10); + emailLayout->addWidget(emailEdit); + emailLayout->addWidget(sendCodeButton); + formLayout->addLayout(emailLayout, row, 1); + row++; + + // 验证码行 + formLayout->addWidget(codeLabel, row, 0, Qt::AlignRight | Qt::AlignVCenter); + QHBoxLayout *codeLayout = new QHBoxLayout(); + codeLayout->setSpacing(10); + codeLayout->addWidget(verificationCodeEdit); + codeLayout->addWidget(timerLabel); + formLayout->addLayout(codeLayout, row, 1); + row++; + + // 用户名行 + formLayout->addWidget(usernameLabel, row, 0, Qt::AlignRight | Qt::AlignVCenter); + formLayout->addWidget(usernameEdit, row, 1); + row++; + + // 年级行 + formLayout->addWidget(gradeLabel, row, 0, Qt::AlignRight | Qt::AlignVCenter); + formLayout->addWidget(gradeComboBox, row, 1); + row++; + + // 密码行 + formLayout->addWidget(passwordLabel, row, 0, Qt::AlignRight | Qt::AlignVCenter); + formLayout->addWidget(passwordEdit, row, 1); + row++; + + // 确认密码行 + formLayout->addWidget(confirmPasswordLabel, row, 0, Qt::AlignRight | Qt::AlignVCenter); + formLayout->addWidget(confirmPasswordEdit, row, 1); + row++; + + // 按钮区域 + QWidget *buttonWidget = new QWidget(); + QHBoxLayout *buttonLayout = new QHBoxLayout(buttonWidget); + buttonLayout->setSpacing(30); + buttonLayout->setContentsMargins(0, 30, 0, 0); + + registerButton = new QPushButton("注册"); + registerButton->setFixedSize(140, 45); + registerButton->setStyleSheet("QPushButton {" + "background-color: #27ae60;" + "color: white;" + "border: none;" + "font-size: 16px;" + "font-weight: bold;" + "border-radius: 6px;" + "}" + "QPushButton:hover {" + "background-color: #229954;" + "}"); + + backButton = new QPushButton("返回登录"); + backButton->setFixedSize(140, 45); + backButton->setStyleSheet("QPushButton {" + "background-color: #95a5a6;" + "color: white;" + "border: none;" + "font-size: 16px;" + "font-weight: bold;" + "border-radius: 6px;" + "}" + "QPushButton:hover {" + "background-color: #7f8c8d;" + "}"); + + buttonLayout->addStretch(); + buttonLayout->addWidget(registerButton); + buttonLayout->addWidget(backButton); + buttonLayout->addStretch(); + + // 添加到主布局 + mainLayout->addWidget(titleLabel); + mainLayout->addLayout(formLayout); + mainLayout->addStretch(); + mainLayout->addWidget(buttonWidget); + + // 初始化计时器 + countdownTimer = new QTimer(this); + countdownTimer->setInterval(1000); + + // 连接信号槽 + connect(sendCodeButton, &QPushButton::clicked, this, &RegisterWidget::onSendCodeClicked); + connect(registerButton, &QPushButton::clicked, this, &RegisterWidget::onRegisterClicked); + connect(backButton, &QPushButton::clicked, this, &RegisterWidget::onBackClicked); + connect(countdownTimer, &QTimer::timeout, this, &RegisterWidget::updateTimer); + + // 初始状态 + resetCountdown(); + + qDebug() << "RegisterWidget: 初始化完成"; +} + +void RegisterWidget::onSendCodeClicked() +{ + QString email = emailEdit->text().trimmed(); + qDebug() << "RegisterWidget: 发送验证码到:" << email; + + if (!validateEmail(email)) { + QMessageBox::warning(this, "输入错误", "请输入有效的邮箱地址"); + return; + } + + // 使用UserManager发送验证码 + std::wstring emailW = email.toStdWString(); + std::wstring generatedCodeW; + + if (userManager.sendVerificationCode(emailW, generatedCodeW)) { + generatedCode = QString::fromStdWString(generatedCodeW); + + QMessageBox::information(this, "验证码已发送", + QString("验证码已发送到 %1\n验证码:%2\n(验证码10分钟内有效)") + .arg(email).arg(QString::fromStdWString(generatedCodeW))); + + // 开始倒计时 + startCountdown(); + } else { + QMessageBox::warning(this, "发送失败", "验证码发送失败,请重试"); + } +} + +void RegisterWidget::onRegisterClicked() +{ + QString email = emailEdit->text().trimmed(); + QString username = usernameEdit->text().trimmed(); + QString verificationCode = verificationCodeEdit->text().trimmed(); + QString password = passwordEdit->text(); + QString confirmPassword = confirmPasswordEdit->text(); + QString grade = gradeComboBox->currentText(); + + qDebug() << "RegisterWidget: 开始注册,用户名:" << username << "年级:" << grade; + + // 验证输入 + if (email.isEmpty() || username.isEmpty() || verificationCode.isEmpty() || + password.isEmpty() || confirmPassword.isEmpty()) { + QMessageBox::warning(this, "输入错误", "请填写所有字段"); + return; + } + + if (!validateEmail(email)) { + QMessageBox::warning(this, "输入错误", "请输入有效的邮箱地址"); + return; + } + + // 验证验证码 - 使用UserManager验证 + std::wstring emailW = email.toStdWString(); + std::wstring codeW = verificationCode.toStdWString(); + + if (!userManager.verifyEmailCode(emailW, codeW)) { + QMessageBox::warning(this, "验证错误", "验证码错误或已过期"); + return; + } + + // 验证密码 + if (password != confirmPassword) { + QMessageBox::warning(this, "输入错误", "两次输入的密码不一致"); + return; + } + + if (!validatePassword(password)) { + QMessageBox::warning(this, "输入错误", + "密码必须为6-10位,且包含大小写字母和数字"); + return; + } + + if (username.length() < 2 || username.length() > 20) { + QMessageBox::warning(this, "输入错误", "用户名长度应在2-20个字符之间"); + return; + } + + // 实际注册用户 - 使用安全的用户名避免编码问题 + std::wstring safeUsername; + for (QChar ch : username) { + if (ch.isLetterOrNumber() || ch == '_') { + safeUsername += ch.unicode(); + } + } + + // 如果用户名不合法,生成一个随机用户名 + if (safeUsername.empty()) { + safeUsername = L"user" + std::to_wstring(1000 + (std::rand() % 9000)); + qDebug() << "RegisterWidget: 生成随机用户名:" << QString::fromStdWString(safeUsername); + } + + qDebug() << "RegisterWidget: 尝试注册用户:" << QString::fromStdWString(safeUsername); + + if (userManager.registerUser(safeUsername, + password.toStdWString(), + grade.toStdWString())) { + qDebug() << "RegisterWidget: 注册成功"; + QMessageBox::information(this, "注册成功", + QString("注册成功!\n用户名: %1\n请使用新账号登录") + .arg(QString::fromStdWString(safeUsername))); + + // 重置表单 + emailEdit->clear(); + usernameEdit->clear(); + verificationCodeEdit->clear(); + passwordEdit->clear(); + confirmPasswordEdit->clear(); + resetCountdown(); + + qDebug() << "RegisterWidget: 发射注册成功信号"; + + // 发射信号 + emit registerSuccess(); + emit showLogin(); + + qDebug() << "RegisterWidget: 信号发射完成"; + + } else { + qDebug() << "RegisterWidget: 注册失败"; + QMessageBox::warning(this, "注册失败", "用户名已存在,请选择其他用户名"); + } +} + +void RegisterWidget::onBackClicked() +{ + qDebug() << "RegisterWidget: 返回登录"; + resetCountdown(); + emit showLogin(); +} + +bool RegisterWidget::validatePassword(const QString &password) +{ + if (password.length() < 6 || password.length() > 10) { + return false; + } + + bool hasUpper = false, hasLower = false, hasDigit = false; + for (QChar ch : password) { + if (ch.isUpper()) hasUpper = true; + else if (ch.isLower()) hasLower = true; + else if (ch.isDigit()) hasDigit = true; + } + + return hasUpper && hasLower && hasDigit; +} + +bool RegisterWidget::validateEmail(const QString &email) +{ + QRegularExpression emailRegex(R"(^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$)"); + return emailRegex.match(email).hasMatch(); +} + +void RegisterWidget::startCountdown() +{ + countdownSeconds = 600; // 10分钟 + sendCodeButton->setEnabled(false); + updateTimer(); + countdownTimer->start(); + qDebug() << "RegisterWidget: 开始倒计时,10分钟"; +} + +void RegisterWidget::resetCountdown() +{ + countdownTimer->stop(); + countdownSeconds = 0; + sendCodeButton->setEnabled(true); + timerLabel->clear(); + generatedCode.clear(); + qDebug() << "RegisterWidget: 重置倒计时"; +} + +void RegisterWidget::updateTimer() +{ + if (countdownSeconds > 0) { + countdownSeconds--; + int minutes = countdownSeconds / 60; + int seconds = countdownSeconds % 60; + timerLabel->setText(QString("%1:%2").arg(minutes, 2, 10, QLatin1Char('0')) + .arg(seconds, 2, 10, QLatin1Char('0'))); + } else { + resetCountdown(); + QMessageBox::information(this, "提示", "验证码已过期,请重新获取"); + } +} diff --git a/src/frontend/registerwidget.h b/src/frontend/registerwidget.h new file mode 100644 index 0000000..ed5084d --- /dev/null +++ b/src/frontend/registerwidget.h @@ -0,0 +1,59 @@ +#ifndef REGISTERWIDGET_H +#define REGISTERWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "usermanage.h" + +class RegisterWidget : public QWidget +{ + Q_OBJECT + +public: + explicit RegisterWidget(QWidget *parent = nullptr); + +signals: + void registerSuccess(); + void showLogin(); + +private slots: + void onSendCodeClicked(); + void onRegisterClicked(); + void onBackClicked(); + void updateTimer(); + +private: + QLineEdit *emailEdit; + QLineEdit *usernameEdit; + QLineEdit *verificationCodeEdit; + QLineEdit *passwordEdit; + QLineEdit *confirmPasswordEdit; + QComboBox *gradeComboBox; + QPushButton *sendCodeButton; + QPushButton *registerButton; + QPushButton *backButton; + QLabel *timerLabel; + + QTimer *countdownTimer; + int countdownSeconds; + QString generatedCode; + + UserManager userManager; // 添加用户管理器 + + bool validatePassword(const QString &password); + bool validateEmail(const QString &email); + void startCountdown(); + void resetCountdown(); +}; + +#endif diff --git a/src/frontend/resultwidget.cpp b/src/frontend/resultwidget.cpp new file mode 100644 index 0000000..23f9f3e --- /dev/null +++ b/src/frontend/resultwidget.cpp @@ -0,0 +1,115 @@ +#include "resultwidget.h" + +ResultWidget::ResultWidget(QWidget *parent) : QWidget(parent) +{ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + + // 标题 + QLabel *titleLabel = new QLabel("考试结果"); + titleLabel->setAlignment(Qt::AlignCenter); + titleLabel->setStyleSheet("font-size: 24px; font-weight: bold; margin: 30px; color: #2c3e50;"); + + // 结果区域 + QWidget *resultWidget = new QWidget(); + QVBoxLayout *resultLayout = new QVBoxLayout(resultWidget); + resultLayout->setContentsMargins(50, 30, 50, 30); + + resultLabel = new QLabel("考试完成!"); + resultLabel->setAlignment(Qt::AlignCenter); + resultLabel->setStyleSheet("font-size: 20px; margin: 20px; color: #34495e;"); + + scoreLabel = new QLabel(); + scoreLabel->setAlignment(Qt::AlignCenter); + scoreLabel->setStyleSheet("font-size: 36px; font-weight: bold; margin: 20px; color: #e74c3c;"); + + percentageLabel = new QLabel(); + percentageLabel->setAlignment(Qt::AlignCenter); + percentageLabel->setStyleSheet("font-size: 18px; margin: 10px; color: #7f8c8d;"); + + // 按钮区域 + QWidget *buttonWidget = new QWidget(); + QHBoxLayout *buttonLayout = new QHBoxLayout(buttonWidget); + + backButton = new QPushButton("返回主菜单"); + backButton->setStyleSheet("QPushButton {" + "background-color: #95a5a6;" + "color: white;" + "border: none;" + "padding: 10px 20px;" + "font-size: 14px;" + "border-radius: 5px;" + "}" + "QPushButton:hover {" + "background-color: #7f8c8d;" + "}"); + backButton->setFixedWidth(120); + + newExamButton = new QPushButton("继续做题"); + newExamButton->setStyleSheet("QPushButton {" + "background-color: #3498db;" + "color: white;" + "border: none;" + "padding: 10px 20px;" + "font-size: 14px;" + "border-radius: 5px;" + "}" + "QPushButton:hover {" + "background-color: #2980b9;" + "}"); + newExamButton->setFixedWidth(120); + + buttonLayout->addStretch(); + buttonLayout->addWidget(backButton); + buttonLayout->addSpacing(20); + buttonLayout->addWidget(newExamButton); + buttonLayout->addStretch(); + + // 添加到结果布局 + resultLayout->addWidget(resultLabel); + resultLayout->addWidget(scoreLabel); + resultLayout->addWidget(percentageLabel); + resultLayout->addSpacing(30); + resultLayout->addWidget(buttonWidget); + + // 添加到主布局 + mainLayout->addWidget(titleLabel); + mainLayout->addWidget(resultWidget); + mainLayout->addStretch(); + + // 连接信号槽 + connect(backButton, &QPushButton::clicked, this, &ResultWidget::onBackClicked); + connect(newExamButton, &QPushButton::clicked, this, &ResultWidget::onNewExamClicked); +} + +void ResultWidget::setResult(int score, int total) +{ + double percentage = (double)score / total * 100; + + scoreLabel->setText(QString("%1 / %2").arg(score).arg(total)); + percentageLabel->setText(QString("正确率: %1%").arg(percentage, 0, 'f', 1)); + + // 根据分数显示不同的评价 + if (percentage >= 90) { + resultLabel->setText("优秀!你的表现非常出色!"); + resultLabel->setStyleSheet("font-size: 20px; margin: 20px; color: #27ae60;"); + } else if (percentage >= 70) { + resultLabel->setText("良好!继续努力!"); + resultLabel->setStyleSheet("font-size: 20px; margin: 20px; color: #f39c12;"); + } else if (percentage >= 60) { + resultLabel->setText("及格!还有提升空间!"); + resultLabel->setStyleSheet("font-size: 20px; margin: 20px; color: #e67e22;"); + } else { + resultLabel->setText("需要加油!再多练习一下吧!"); + resultLabel->setStyleSheet("font-size: 20px; margin: 20px; color: #e74c3c;"); + } +} + +void ResultWidget::onBackClicked() +{ + emit backToMenu(); +} + +void ResultWidget::onNewExamClicked() +{ + emit startNewExam(); +} diff --git a/src/frontend/resultwidget.h b/src/frontend/resultwidget.h new file mode 100644 index 0000000..0343e7e --- /dev/null +++ b/src/frontend/resultwidget.h @@ -0,0 +1,34 @@ +#ifndef RESULTWIDGET_H +#define RESULTWIDGET_H + +#include +#include +#include +#include +#include + +class ResultWidget : public QWidget +{ + Q_OBJECT + +public: + explicit ResultWidget(QWidget *parent = nullptr); + void setResult(int score, int total); + +signals: + void backToMenu(); + void startNewExam(); + +private slots: + void onBackClicked(); + void onNewExamClicked(); + +private: + QLabel *resultLabel; + QLabel *scoreLabel; + QLabel *percentageLabel; + QPushButton *backButton; + QPushButton *newExamButton; +}; + +#endif