250 lines
8.4 KiB
Python
250 lines
8.4 KiB
Python
# 班级管理路由
|
|
|
|
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, User
|
|
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/teachers", methods=["GET"])
|
|
@login_required_json
|
|
def api_teachers_list():
|
|
"""用户列表(用于班主任选择,所有登录用户可访问)"""
|
|
users = User.query.order_by(User.name, User.username).all()
|
|
return jsonify([{"id": u.id, "name": u.name or u.username} for u in users])
|
|
|
|
|
|
@main_bp.route("/api/classes", methods=["GET"])
|
|
@login_required_json
|
|
def api_classes_list():
|
|
"""班级列表"""
|
|
# 支持筛选参数: active=true/false, mine=true/false
|
|
active_filter = request.args.get("active")
|
|
mine_filter = request.args.get("mine")
|
|
|
|
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)
|
|
|
|
# 我的班级筛选(当前用户作为老师的班级)
|
|
if mine_filter and mine_filter.lower() == "true":
|
|
user_id = session.get("user_id")
|
|
if user_id:
|
|
query = query.filter(Class.teacher_id == user_id)
|
|
|
|
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", "")
|
|
teacher_id = data.get("teacher_id") # 可以为空
|
|
level = data.get("level", "启蒙")
|
|
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, teacher_id=teacher_id, level=level, 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/<int:class_id>", 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 "teacher_id" in data:
|
|
cls.teacher_id = data["teacher_id"] if data["teacher_id"] else None
|
|
|
|
if "level" in data:
|
|
cls.level = data["level"]
|
|
|
|
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/<int:class_id>", 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/<int:class_id>/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/<int:class_id>/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/<int:class_id>/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
|