# 班级管理路由 from flask import request, jsonify, render_template, session from datetime import datetime, timedelta from app.routes import main_bp from app.models import db, Class, Student, StudentGoal, Goal from app.routes.auth import login_required_json, admin_required import logging logger = logging.getLogger(__name__) @main_bp.route("/classes") @login_required_json def classes_page(): """班级管理页面""" return render_template("classes.html", active_nav="classes") @main_bp.route("/api/classes", methods=["GET"]) @login_required_json def api_classes_list(): """班级列表""" # 支持筛选参数: active=true, active=false, 不传则返回全部 active_filter = request.args.get("active") query = Class.query if active_filter is not None: if active_filter.lower() == "true": query = query.filter(Class.active == True) elif active_filter.lower() == "false": query = query.filter(Class.active == False) classes = query.order_by(Class.created_at.desc()).all() return jsonify([c.to_dict() for c in classes]) @main_bp.route("/api/classes", methods=["POST"]) @admin_required def api_classes_create(): """新增班级""" data = request.get_json() name = data.get("name", "").strip() description = data.get("description", "") active = data.get("active", True) # 默认进行中 if not name: return jsonify({"error": "请输入班级名称", "code": "VALIDATION_ERROR"}), 400 if Class.query.filter_by(name=name).first(): return jsonify({"error": "班级名称已存在", "code": "DUPLICATE_NAME"}), 400 try: cls = Class(name=name, description=description, active=active) db.session.add(cls) db.session.commit() return jsonify(cls.to_dict()) except Exception as e: db.session.rollback() logger.error(f"创建班级失败: {str(e)}") return jsonify({"error": "操作失败,请稍后重试", "code": "SERVER_ERROR"}), 500 @main_bp.route("/api/classes/", methods=["PUT"]) @admin_required def api_classes_update(class_id): """编辑班级""" cls = Class.query.get_or_404(class_id) data = request.get_json() if "name" in data and data["name"]: # 检查名称是否与其他班级冲突 existing = Class.query.filter_by(name=data["name"]).first() if existing and existing.id != class_id: return jsonify({"error": "班级名称已存在", "code": "DUPLICATE_NAME"}), 400 cls.name = data["name"] if "description" in data: cls.description = data["description"] if "active" in data: cls.active = data["active"] try: db.session.commit() return jsonify(cls.to_dict()) except Exception as e: db.session.rollback() logger.error(f"更新班级失败: {str(e)}") return jsonify({"error": "操作失败,请稍后重试", "code": "SERVER_ERROR"}), 500 @main_bp.route("/api/classes/", methods=["DELETE"]) @admin_required def api_classes_delete(class_id): """删除班级(不删除学员,仅解除关联)""" cls = Class.query.get_or_404(class_id) try: # 解除学员与班级的关联 Student.query.filter_by(class_id=class_id).update({"class_id": None}) db.session.delete(cls) db.session.commit() return jsonify({"message": "删除成功"}) except Exception as e: db.session.rollback() logger.error(f"删除班级失败: {str(e)}") return jsonify({"error": "操作失败,请稍后重试", "code": "SERVER_ERROR"}), 500 @main_bp.route("/api/classes//students", methods=["GET"]) @login_required_json def api_classes_students(class_id): """班级学员列表""" cls = Class.query.get_or_404(class_id) # 直接查询而非使用relationship students = Student.query.filter_by(class_id=class_id).all() return jsonify( [ { "id": s.id, "name": s.name, "phone": s.phone, "practice_time": s.practice_time, } for s in students ] ) @main_bp.route("/api/classes//assign", methods=["POST"]) @login_required_json def api_classes_assign(class_id): """分配学员到班级""" cls = Class.query.get_or_404(class_id) data = request.get_json() student_ids = data.get("student_ids", []) try: # 1. 先清除这个班级的所有学员关联 Student.query.filter_by(class_id=class_id).update({"class_id": None}) # 2. 再分配选中的学员到这个班级 if student_ids: Student.query.filter(Student.id.in_(student_ids)).update( {"class_id": class_id} ) db.session.commit() return jsonify({"message": "分配成功"}) except Exception as e: db.session.rollback() logger.error(f"分配学员失败: {str(e)}") return jsonify({"error": "操作失败,请稍后重试", "code": "SERVER_ERROR"}), 500 @main_bp.route("/api/classes//goals", methods=["POST"]) @login_required_json def api_classes_assign_goals(class_id): """批量分配目标给班级所有学员""" cls = Class.query.get_or_404(class_id) data = request.get_json() goal_id = data.get("goal_id") assessment_days = data.get("assessment_days") assessment_date = data.get("assessment_date") start_date = data.get("start_date") start_now = data.get("start_now", True) if not goal_id: return jsonify({"error": "请选择目标", "code": "VALIDATION_ERROR"}), 400 if not assessment_days and not assessment_date: return jsonify({"error": "请选择评估方式", "code": "VALIDATION_ERROR"}), 400 # 检查目标是否存在 goal = Goal.query.get(goal_id) if not goal: return jsonify({"error": "目标不存在", "code": "NOT_FOUND"}), 404 # 获取班级所有学员 students = Student.query.filter_by(class_id=class_id).all() if not students: return jsonify({"error": "班级没有学员", "code": "NO_STUDENTS"}), 400 # 处理评估日期 final_assessment_date = None if assessment_date: final_assessment_date = datetime.fromisoformat(assessment_date) elif assessment_days: final_assessment_date = datetime.now() + timedelta(days=int(assessment_days)) # 处理开始日期 final_start_date = None if start_date: final_start_date = datetime.fromisoformat(start_date) elif start_now: final_start_date = datetime.now() # 批量创建 StudentGoal assigned_count = 0 skipped = [] for student in students: # 检查是否已分配 existing = StudentGoal.query.filter_by(student_id=student.id, goal_id=goal_id).first() if existing: skipped.append(student.name) continue record = StudentGoal( student_id=student.id, goal_id=goal_id, start_date=final_start_date, assessment_date=final_assessment_date ) db.session.add(record) assigned_count += 1 try: db.session.commit() result = {"message": f"成功分配 {assigned_count} 个学员", "assigned": assigned_count} if skipped: result["skipped"] = skipped result["skipped_count"] = len(skipped) return jsonify(result) except Exception as e: db.session.rollback() logger.error(f"分配目标失败: {str(e)}") return jsonify({"error": "操作失败,请稍后重试", "code": "SERVER_ERROR"}), 500