diff --git a/README.md b/README.md index 9c3ce26..2af99f3 100644 --- a/README.md +++ b/README.md @@ -161,4 +161,4 @@ piano-plan/ > **版本**:v1.2.0 > **创建时间**:2026-04-17 -> **最后更新**:2026-04-21 +> **最后更新**:2026-04-23 diff --git a/app/__init__.py b/app/__init__.py index 24ce119..fd92061 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -25,14 +25,6 @@ def create_app(): ) app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False - # 问题文件目录 - is_docker = os.environ.get("FLASK_ENV") == "production" - if is_docker: - app.config["PROBLEMS_DIR"] = BASE_DIR / "个性化方案" - else: - # 本地开发:问题文件在父目录的 个性化方案/针对性练习(拆分为单独文件) - app.config["PROBLEMS_DIR"] = BASE_DIR.parent / "个性化方案" / "针对性练习(拆分为单独文件)" - app.config["PDF_OUTPUT_DIR"] = BASE_DIR / "output" app.config["API_CONFIG_FILE"] = BASE_DIR / "config" / "api_config.json" @@ -109,11 +101,26 @@ def create_app(): if "sort_order" not in template_columns: db.session.execute(text("ALTER TABLE templates ADD COLUMN sort_order INTEGER DEFAULT 0")) db.session.commit() + + # 检查practice_plans表是否有template_id字段 + result5 = db.session.execute(text("PRAGMA table_info(practice_plans)")) + plan_columns = [row[1] for row in result5] + if "template_id" not in plan_columns: + db.session.execute(text("ALTER TABLE practice_plans ADD COLUMN template_id INTEGER REFERENCES templates(id)")) + db.session.commit() + + # 检查practice_plans表是否有is_typical字段 + result6 = db.session.execute(text("PRAGMA table_info(practice_plans)")) + plan_columns2 = [row[1] for row in result6] + if "is_typical" not in plan_columns2: + db.session.execute(text("ALTER TABLE practice_plans ADD COLUMN is_typical INTEGER DEFAULT 0")) + db.session.commit() except Exception as e: print(f"数据库迁移: {e}") # 初始化默认模板(必须在迁移之后) - from app.routes.templates import init_default_templates - init_default_templates() + # 已禁用:如果需要默认模板,请手动创建 + # from app.routes.templates import init_default_templates + # init_default_templates() return app diff --git a/app/models.py b/app/models.py index 977189e..f9daa46 100644 --- a/app/models.py +++ b/app/models.py @@ -114,6 +114,15 @@ class Student(db.Model): class_obj = db.relationship("Class", backref="students") def to_dict(self): + # 获取问题列表,按严重程度排序(严重 > 中等 > 轻微) + severity_order = {"严重": 0, "中等": 1, "轻微": 2} + problems_list = sorted( + self.problems.all(), + key=lambda p: (severity_order.get(p.severity, 1), p.created_at) + ) + # 通过关联获取问题名称 + problem_names = [p.problem.name if p.problem else p.problem_name for p in problems_list] + return { "id": self.id, "name": self.name, @@ -127,10 +136,33 @@ class Student(db.Model): if self.created_at else None, "problem_count": self.problems.count(), + "problem_names": problem_names, # 问题名称列表(按严重程度排序) "plan_count": self.plans.count(), } +class Problem(db.Model): + """问题表""" + + __tablename__ = "problems" + + id = db.Column(db.Integer, primary_key=True) + no = db.Column(db.String(10), unique=True, nullable=False) # 编号:01, 02... + name = db.Column(db.String(100), nullable=False) # 问题名称 + category = db.Column(db.String(50), default="技术类") # 分类 + content = db.Column(db.Text) # 问题详细内容 + created_at = db.Column(db.DateTime, default=datetime.now) + updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now) + + def to_dict(self): + return { + "id": self.id, + "no": self.no, + "name": self.name, + "category": self.category, + } + + class StudentProblem(db.Model): """学员问题记录表""" @@ -138,17 +170,21 @@ class StudentProblem(db.Model): id = db.Column(db.Integer, primary_key=True) student_id = db.Column(db.Integer, db.ForeignKey("students.id"), nullable=False) - problem_id = db.Column(db.String(50), nullable=False) # 如 "01_手小" - problem_name = db.Column(db.String(100), nullable=False) # 如 "手小" + problem_id = db.Column(db.Integer, db.ForeignKey("problems.id"), nullable=False) # 外键 severity = db.Column(db.String(10), nullable=False) # 轻微/中等/严重 level = db.Column(db.String(20)) # 启蒙/入门/进阶/熟练/精通 created_at = db.Column(db.DateTime, default=datetime.now) + # 关联到 Problem + problem = db.relationship("Problem", foreign_keys=[problem_id]) + def to_dict(self): return { "id": self.id, - "problem_id": self.problem_id, - "problem_name": self.problem_name, + "student_id": self.student_id, + "problem_id": self.problem_id, # 外键关联到 problems.id + "problem_name": self.problem.name if self.problem else None, + "problem_no": self.problem.no if self.problem else None, "severity": self.severity, "level": self.level, } @@ -161,14 +197,43 @@ class PracticePlan(db.Model): id = db.Column(db.Integer, primary_key=True) student_id = db.Column(db.Integer, db.ForeignKey("students.id"), nullable=False) + template_id = db.Column(db.Integer, db.ForeignKey("templates.id"), nullable=True) # 使用的AI提示词模板 + is_typical = db.Column(db.Boolean, default=False, nullable=False) # 是否为典型方案 content = db.Column(db.Text, nullable=False) # JSON格式存储方案内容 created_at = db.Column(db.DateTime, default=datetime.now) + # 关联 + template = db.relationship("Template", foreign_keys=[template_id]) + def to_dict(self): + import json as json_module + content_obj = {} + try: + content_obj = json_module.loads(self.content) if self.content else {} + except: + pass + + # 从 content 中提取问题列表 + problems = content_obj.get("problems", []) + problem_names = [p.get("name", "") for p in problems] if problems else [] + + # 获取模板名称 + template_name = self.template.name if self.template else None + + # 获取学员班级 + class_name = None + if self.student and self.student.class_obj: + class_name = self.student.class_obj.name + return { "id": self.id, "student_id": self.student_id, "student_name": self.student.name if self.student else "", + "class_name": class_name, + "template_id": self.template_id, + "template_name": template_name, + "is_typical": self.is_typical, + "problem_names": problem_names, "created_at": self.created_at.strftime("%Y-%m-%d %H:%M") if self.created_at else None, diff --git a/app/routes/classes.py b/app/routes/classes.py index bc7d400..f3956c2 100644 --- a/app/routes/classes.py +++ b/app/routes/classes.py @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) @login_required_json def classes_page(): """班级管理页面""" - return render_template("classes.html") + return render_template("classes.html", active_nav="classes") @main_bp.route("/api/classes", methods=["GET"]) diff --git a/app/routes/plans.py b/app/routes/plans.py index 3459e11..357b509 100644 --- a/app/routes/plans.py +++ b/app/routes/plans.py @@ -13,10 +13,10 @@ from flask import ( session, ) from app.routes import main_bp -from app.models import db, Student, PracticePlan +from app.models import db, Student, PracticePlan, StudentProblem from app.services.plan_generator import generate_practice_plan, generate_ai_report from app.services.pdf_generator import generate_pdf -from app.routes.auth import login_required_json +from app.routes.auth import login_required_json, admin_required def sse_format(data): @@ -33,6 +33,114 @@ def get_student_plans(student_id): return jsonify([p.to_dict() for p in plans]) +@main_bp.route("/api/plans", methods=["GET"]) +@login_required_json +def get_all_plans(): + """获取所有方案(支持多条件筛选) + + 查询参数: + - class_id: 班级ID + - problem_ids: Problem.id 列表,逗号分隔 + - template_id: 模板ID + - is_typical: 是否典型 (true/false) + - student_name: 学员姓名(模糊匹配) + """ + import json as json_module + + query = PracticePlan.query + + # 按班级筛选 + class_id = request.args.get('class_id', type=int) + if class_id: + query = query.join(Student).filter(Student.class_id == class_id) + + # 按模板筛选 + template_id = request.args.get('template_id', type=int) + if template_id: + query = query.filter(PracticePlan.template_id == template_id) + + # 按典型状态筛选 + is_typical = request.args.get('is_typical') + if is_typical and is_typical.lower() == 'true': + query = query.filter(PracticePlan.is_typical == True) + + # 按学员姓名模糊筛选 + student_name = request.args.get('student_name') + if student_name: + query = query.join(Student).filter(Student.name.like(f'%%{student_name}%%')) + + # 按问题筛选(通过 problem_id 关联到学员的问题) + problem_ids = request.args.get('problem_ids') + if problem_ids: + problem_id_list = [int(pid.strip()) for pid in problem_ids.split(',') if pid.strip()] + if problem_id_list: + # 筛选:方案对应的学员有指定问题之一的 + # 使用子查询避免笛卡尔积导致的重复 + from sqlalchemy import exists + query = query.join(Student).filter( + exists().where( + (StudentProblem.student_id == Student.id) & + (StudentProblem.problem_id.in_(problem_id_list)) + ) + ) + + plans = query.order_by(PracticePlan.created_at.desc()).all() + return jsonify([p.to_dict() for p in plans]) + + +@main_bp.route("/api/plans//typical", methods=["POST"]) +@login_required_json +def toggle_plan_typical(plan_id): + """切换方案的典型状态""" + plan = PracticePlan.query.get_or_404(plan_id) + plan.is_typical = not plan.is_typical + db.session.commit() + return jsonify({"success": True, "is_typical": plan.is_typical}) + + +@main_bp.route("/plans") +@login_required_json +def plans_page(): + """方案管理页面""" + return render_template("plans.html", active_nav="plans") + + +@main_bp.route("/api/admin/fix-plan-templates", methods=["GET"]) +@admin_required +def fix_plan_templates(): + """临时接口:修复所有方案的模板关联""" + from app.models import Template, PracticePlan + + simple_template = Template.query.filter_by(name='简单文字版').first() + formal_template = Template.query.filter_by(name='正式报告版').first() + + if not simple_template or not formal_template: + return jsonify({"error": "模板没找到"}), 400 + + plans = PracticePlan.query.order_by(PracticePlan.created_at.desc()).all() + if plans: + plans[0].template_id = simple_template.id + for plan in plans[1:]: + plan.template_id = formal_template.id + db.session.commit() + + return jsonify({"ok": True, "updated": len(plans)}) + + +@main_bp.route("/plan/") +@login_required_json +def plan_detail_page(plan_id): + """方案详情页面""" + return render_template("plan_detail.html", active_nav="plans") + + +@main_bp.route("/plan//edit") +@login_required_json +def plan_edit_page(plan_id): + """方案编辑页面""" + return render_template("plan_edit.html", active_nav="plans", plan_id=plan_id) + + @main_bp.route("/api/generate-plan", methods=["POST"]) @login_required_json def generate_plan(): @@ -49,30 +157,24 @@ def generate_plan(): return jsonify({"error": "请先记录学员的问题"}), 400 # 预先收集所有数据,避免在generator中访问数据库 - problems_dir = current_app.config["PROBLEMS_DIR"] - # 学员的统一练习时间 practice_time = student.practice_time or "30-60分钟" problem_data = [] for p in problems: - # problem_id 已经是完整标识(如 "01_手小"),直接用作文件名 - problem_file = os.path.join(problems_dir, f"{p.problem_id}.md") - - content = "" - if os.path.exists(problem_file): - with open(problem_file, "r", encoding="utf-8") as f: - content = f.read() - - problem_data.append( - { - "problem_id": p.problem_id, - "problem_name": p.problem_name, - "severity": p.severity, - "level": p.level, - "content": content, - } - ) + # 使用 Problem 关联获取问题信息 + problem_obj = p.problem + if problem_obj: + problem_data.append( + { + "problem_id": problem_obj.id, # 使用 Problem.id + "problem_name": problem_obj.name, + "problem_no": problem_obj.no, + "severity": p.severity, + "level": p.level, + "content": problem_obj.content or "", + } + ) time_mapping = { "15分钟": {"total": 15, "basic": 10, "tech": 2, "piece": 3}, "30分钟": {"total": 30, "basic": 15, "tech": 5, "piece": 10}, @@ -104,7 +206,6 @@ def generate_plan(): plan_content = generate_practice_plan( student_name=student.name, problems=problem_data, - problems_dir=problems_dir, practice_time=practice_time, ) @@ -241,6 +342,7 @@ def generate_plan(): try: plan = PracticePlan( student_id=student_id, + template_id=template_id, content=json.dumps(plan_content, ensure_ascii=False), ) db.session.add(plan) diff --git a/app/routes/problems.py b/app/routes/problems.py index 083eeda..27627ac 100644 --- a/app/routes/problems.py +++ b/app/routes/problems.py @@ -33,7 +33,7 @@ def add_student_problem(student_id): # 添加或更新问题 for p in problems: - problem_id = p.get("problem_id") + problem_id = p.get("problem_id") # 这是 problems.id submitted_ids.add(problem_id) # 检查是否已存在 @@ -50,7 +50,6 @@ def add_student_problem(student_id): problem = StudentProblem( student_id=student_id, problem_id=problem_id, - problem_name=p.get("problem_name"), severity=p.get("severity"), level=p.get("level"), ) @@ -70,13 +69,38 @@ def clear_student_problems(student_id): @main_bp.route( - "/api/students//problems/", methods=["DELETE"] + "/api/students//problems/", methods=["DELETE"] ) @login_required_json -def delete_single_problem(student_id, problem_id): +def delete_single_problem(student_id, student_problem_id): """删除学员的单个问题""" StudentProblem.query.filter_by( - student_id=student_id, problem_id=problem_id + id=student_problem_id ).delete() db.session.commit() return jsonify({"message": "删除成功"}) + + +@main_bp.route( + "/api/students//problems/", methods=["PUT"] +) +@login_required_json +def update_single_problem(student_id, student_problem_id): + """更新学员的单个问题(严重程度和级别)""" + student = Student.query.get_or_404(student_id) + data = request.get_json() + + problem = StudentProblem.query.filter_by( + id=student_problem_id + ).first() + + if not problem: + return jsonify({"error": "问题记录不存在"}), 404 + + if "severity" in data: + problem.severity = data["severity"] + if "level" in data: + problem.level = data["level"] + + db.session.commit() + return jsonify({"message": "更新成功", "problem": problem.to_dict()}) diff --git a/app/routes/settings.py b/app/routes/settings.py index a38082d..57353d1 100644 --- a/app/routes/settings.py +++ b/app/routes/settings.py @@ -5,6 +5,7 @@ import shutil from datetime import datetime from flask import request, jsonify, render_template, current_app, session, redirect from app.routes import main_bp +from app.models import db, Problem, StudentProblem from app.config import load_api_config, save_api_config from app.routes.auth import login_required_json, admin_required @@ -13,14 +14,14 @@ from app.routes.auth import login_required_json, admin_required @login_required_json def settings(): """问题配置页面 - 所有登录用户可访问""" - return render_template("settings.html") + return render_template("settings.html", active_nav="settings") @main_bp.route("/api-settings") @admin_required def api_settings_page(): """API设置页面 - 仅管理员""" - return render_template("api_settings.html") + return render_template("api_settings.html", active_nav="api-settings") # ==================== API配置接口 ==================== @@ -81,241 +82,99 @@ def update_api_config(): @main_bp.route("/api/problems", methods=["GET"]) @login_required_json def get_problems(): - """获取问题列表(从文件夹动态读取)""" - problems_dir = current_app.config.get("PROBLEMS_DIR") + """获取问题列表(从数据库读取)""" + from app.models import Problem - problems = [] - if problems_dir and os.path.exists(problems_dir): - for filename in sorted(os.listdir(problems_dir)): - if ( - filename.endswith(".md") - and not filename.startswith("模板") - and not filename.startswith("针对性练习建议") - ): - name = filename.replace(".md", "") - if "汇总" in name: - continue - - parts = name.split("_", 1) - if len(parts) == 2: - problem_id = parts[0] - problem_name = parts[1] - - category = "技术类" - filepath = os.path.join(problems_dir, filename) - try: - with open(filepath, "r", encoding="utf-8") as f: - content = f.read() - if "认知类" in content: - category = "认知类" - elif "节奏类" in content: - category = "节奏类" - elif "表现类" in content: - category = "表现类" - elif "习惯类" in content: - category = "习惯类" - elif "综合类" in content: - category = "综合类" - except: - pass - - try: - mtime = os.path.getmtime(filepath) - except: - mtime = 0 - - problems.append( - { - "id": problem_id, - "name": problem_name, - "category": category, - "file": filename, - "mtime": mtime, - } - ) - - return jsonify(sorted(problems, key=lambda x: x["id"])) + problems = Problem.query.order_by(Problem.no).all() + return jsonify([p.to_dict() for p in problems]) -@main_bp.route("/api/problems/", methods=["GET"]) +@main_bp.route("/api/problems/", methods=["GET"]) @login_required_json def get_problem_detail(problem_id): """获取单个问题的详细信息""" - problems_dir = current_app.config.get("PROBLEMS_DIR") + from app.models import Problem - if problems_dir and os.path.exists(problems_dir): - for filename in os.listdir(problems_dir): - if filename.startswith(f"{problem_id}_") and filename.endswith(".md"): - filepath = os.path.join(problems_dir, filename) - with open(filepath, "r", encoding="utf-8") as f: - content = f.read() - return jsonify( - {"id": problem_id, "filename": filename, "content": content} - ) + problem = Problem.query.get(problem_id) + if not problem: + return jsonify({"error": "问题不存在"}), 404 - return jsonify({"error": "问题不存在"}), 404 + return jsonify({ + "id": problem.id, + "no": problem.no, + "name": problem.name, + "category": problem.category, + "content": problem.content, + }) @main_bp.route("/api/problems", methods=["POST"]) @admin_required def create_problem(): """创建新问题 - 仅管理员""" - """创建新问题""" + from app.models import Problem + data = request.get_json() - problem_id = data.get("id", "").strip() - problem_name = data.get("name", "").strip() + no = data.get("no", "").strip() + name = data.get("name", "").strip() category = data.get("category", "技术类") + content = data.get("content", "") - if not problem_id or not problem_name: - return jsonify({"error": "ID和名称不能为空"}), 400 + if not no or not name: + return jsonify({"error": "编号和名称不能为空"}), 400 - # 格式化ID - problem_id = problem_id.zfill(2) + # 检查编号是否已存在 + existing = Problem.query.filter_by(no=no).first() + if existing: + return jsonify({"error": "该编号已存在"}), 400 - problems_dir = current_app.config.get("PROBLEMS_DIR") - filename = f"{problem_id}_{problem_name}.md" - filepath = os.path.join(problems_dir, filename) + # 创建问题 + problem = Problem(no=no, name=name, category=category, content=content) + db.session.add(problem) + db.session.commit() - if os.path.exists(filepath): - return jsonify({"error": "问题已存在"}), 400 - - # 生成默认内容 - content = f"""# {problem_name} - -> 所属系列:钢琴学习常见问题针对性练习建议 -> 配合目标体系使用,针对性补齐短板 - ---- - -## 问题表现 - -- 请描述具体表现症状 - -## 原因分析 - -- 可能的原因1 -- 可能的原因2 - -## 针对性练习方案 - -### 日常基础练习 - -| 练习名称 | 时长 | 频率 | 目的 | -|---------|------|------|------| -| 练习1 | 10分钟 | 每天 | 目的1 | -| 练习2 | 5分钟 | 每天 | 目的2 | - -### 具体操作 - -``` -练习1:练习名称 -- 步骤1 -- 步骤2 -- 步骤3 -``` - -## 练习提醒 - -### ⚠️ 禁忌 - -- 禁忌1 -- 禁忌2 - -### ✓ 正确做法 - -- 正确做法1 -- 正确做法2 - -## 评估标准 - -| 等级 | 标准 | -|------|------| -| 入门 | 达到的标准 | -| 进阶 | 达到的标准 | -| 熟练 | 达到的标准 | -| 精通 | 达到的标准 | - ---- - -> **版本**:V1.0 -> **创建时间**:{datetime.now().strftime("%Y-%m-%d")} -> **适用场景**:成人钢琴集体课学员个性化辅导""" - - with open(filepath, "w", encoding="utf-8") as f: - f.write(content) - - return jsonify({"message": "创建成功", "id": problem_id, "name": problem_name}) + return jsonify({"message": "创建成功", "id": problem.id, "no": problem.no, "name": problem.name}) -@main_bp.route("/api/problems/", methods=["PUT"]) +@main_bp.route("/api/problems/", methods=["PUT"]) @login_required_json def update_problem(problem_id): """更新问题""" - data = request.get_json() - new_name = data.get("name", "").strip() - new_content = data.get("content", "").strip() + from app.models import Problem - if not new_name: - return jsonify({"error": "名称不能为空"}), 400 - - problems_dir = current_app.config.get("PROBLEMS_DIR") - - # 找到旧文件 - old_filename = None - for f in os.listdir(problems_dir): - if f.startswith(f"{problem_id}_") and f.endswith(".md"): - old_filename = f - break - - if not old_filename: + problem = Problem.query.get(problem_id) + if not problem: return jsonify({"error": "问题不存在"}), 404 - old_filepath = os.path.join(problems_dir, old_filename) - new_filename = f"{problem_id}_{new_name}.md" - new_filepath = os.path.join(problems_dir, new_filename) - - # 如果名称改变,需要重命名文件 - if old_filename != new_filename: - if os.path.exists(new_filepath): - return jsonify({"error": "同名问题已存在"}), 400 - os.rename(old_filepath, new_filepath) - filepath = new_filepath - else: - filepath = old_filepath - - # 更新内容 - with open(filepath, "w", encoding="utf-8") as f: - f.write(new_content) + data = request.get_json() + if "name" in data: + problem.name = data["name"].strip() + if "category" in data: + problem.category = data["category"] + if "content" in data: + problem.content = data["content"] + db.session.commit() return jsonify({"message": "更新成功"}) -@main_bp.route("/api/problems/", methods=["DELETE"]) +@main_bp.route("/api/problems/", methods=["DELETE"]) @admin_required def delete_problem(problem_id): """删除问题""" - problems_dir = current_app.config.get("PROBLEMS_DIR") + from app.models import Problem - # 找到文件 - filename = None - for f in os.listdir(problems_dir): - if f.startswith(f"{problem_id}_") and f.endswith(".md"): - filename = f - break - - if not filename: + problem = Problem.query.get(problem_id) + if not problem: return jsonify({"error": "问题不存在"}), 404 - filepath = os.path.join(problems_dir, filename) + # 检查是否有关联数据 + from app.models import StudentProblem + if StudentProblem.query.filter_by(problem_db_id=problem_id).first(): + return jsonify({"error": "该问题已被学员使用,无法删除"}), 400 - # 移动到备份目录 - trash_dir = os.path.join(problems_dir, "bk") - os.makedirs(trash_dir, exist_ok=True) - - import time - - backup_name = f"{filename.replace('.md', '')}_{int(time.time())}.md" - shutil.move(filepath, os.path.join(trash_dir, backup_name)) + db.session.delete(problem) + db.session.commit() return jsonify({"message": "删除成功"}) diff --git a/app/routes/students.py b/app/routes/students.py index 9dd33ad..b4d755b 100644 --- a/app/routes/students.py +++ b/app/routes/students.py @@ -15,18 +15,46 @@ from app.routes import main_bp from app.models import ( db, Student, + Class, + PracticePlan, PROBLEM_LIST, SEVERITY_LEVELS, PRACTICE_TIME_OPTIONS, User, - Class, ) from app.routes.auth import login_required_json, check_login @main_bp.route("/") def index(): - """首页 - 学员列表""" + """首页""" + if not check_login(): + return redirect("/login") + + student_count = Student.query.count() + class_count = Class.query.count() + plan_count = PracticePlan.query.count() + + return render_template( + "home.html", + student_count=student_count, + class_count=class_count, + plan_count=plan_count, + active_nav="home", + ) + + +@main_bp.route("/student/") +@login_required_json +def student_detail_page(student_id): + """学员详情页""" + student = Student.query.get_or_404(student_id) + return render_template("student.html", student=student, active_nav="students") + + +@main_bp.route("/students") +def students_page(): + """学员列表页""" if not check_login(): return redirect("/login") @@ -35,6 +63,7 @@ def index(): problem_list=PROBLEM_LIST, severity_levels=SEVERITY_LEVELS, practice_time_options=PRACTICE_TIME_OPTIONS, + active_nav="students", ) diff --git a/app/routes/templates.py b/app/routes/templates.py index e7256f7..266d353 100644 --- a/app/routes/templates.py +++ b/app/routes/templates.py @@ -86,7 +86,7 @@ def init_default_templates(): def templates_page(): """模板管理页面""" from flask import render_template - return render_template("templates.html") + return render_template("templates.html", active_nav="templates") @templates_bp.route("/templates", methods=["GET"]) diff --git a/app/routes/users.py b/app/routes/users.py index e639c97..ec33af7 100644 --- a/app/routes/users.py +++ b/app/routes/users.py @@ -10,7 +10,7 @@ from app.routes.auth import login_required_json, admin_required @admin_required def users_page(): """用户管理页面""" - return render_template("users.html") + return render_template("users.html", active_nav="users") @main_bp.route("/api/users", methods=["GET"]) diff --git a/app/services/plan_generator.py b/app/services/plan_generator.py index 2e74cdb..c3acf71 100644 --- a/app/services/plan_generator.py +++ b/app/services/plan_generator.py @@ -7,15 +7,14 @@ from app.config import load_api_config def generate_practice_plan( - student_name, problems, problems_dir, practice_time="30-60分钟" + student_name, problems, practice_time="30-60分钟" ): """ 根据学员问题和练习时间生成针对性练习方案 Args: student_name: 学员姓名 - problems: 问题列表 [{problem_id, problem_name, severity}] - problems_dir: 问题文件所在目录 + problems: 问题列表 [{problem_id, problem_name, severity, level, content}] practice_time: 练习时间描述 Returns: @@ -34,39 +33,20 @@ def generate_practice_plan( } time_config = time_mapping.get(practice_time, time_mapping["30分钟"]) - # 读取问题文件内容 + # 从数据库问题内容构建 problem_contents = [] for p in problems: - problem_file = os.path.join(problems_dir, f"{p['problem_id']}.md") - if os.path.exists(problem_file): - with open(problem_file, "r", encoding="utf-8") as f: - content = f.read() - # 提取关键部分 - problem_contents.append( - { - "name": p["problem_name"], - "severity": p["severity"], - "content": _extract_key_sections(content), - "time_allocation": _calculate_time_allocation( - p["severity"], time_config - ), - } - ) - else: - # 问题文件不存在时,使用默认内容 - problem_contents.append( - { - "name": p["problem_name"], - "severity": p["severity"], - "content": { - "problem": f"针对{p['problem_name']}的练习", - "suggestion": "建议每天进行针对性练习", - }, - "time_allocation": _calculate_time_allocation( - p["severity"], time_config - ), - } - ) + content = p.get("content", "") or "" + problem_contents.append( + { + "name": p["problem_name"], + "severity": p["severity"], + "content": _extract_key_sections(content) if content else {"problem": f"针对{p['problem_name']}的练习"}, + "time_allocation": _calculate_time_allocation( + p["severity"], time_config + ), + } + ) # 生成每日练习计划 daily_plan = _generate_daily_schedule(time_config, problem_contents) diff --git a/app/templates/base.html b/app/templates/base.html index 2639f0f..2acf19c 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -8,6 +8,8 @@ + + {% block extra_css %}{% endblock %} {% endblock %} {% block sidebar_nav %} - + 学员管理 - + + 方案管理 + + 问题配置 - - - 班级管理 - +
- + 修改密码 - + 退出登录 {% endblock %} @@ -90,8 +93,8 @@ @@ -232,7 +235,7 @@