feat: 增加学生信息管理功能,优化操行分历史记录展示并更新使用文档

This commit is contained in:
2026-04-23 09:41:56 +08:00
parent 1c5da5dfaa
commit 684adbd718
15 changed files with 447 additions and 24 deletions

View File

@@ -81,7 +81,8 @@ class ConductModel:
limit: int = 100,
offset: int = 0,
start_date: str = None,
end_date: str = None
end_date: str = None,
student_id: int = None
) -> List[Dict[str, Any]]:
"""获取所有记录(班主任/班长专用)"""
# 空字符串转为None
@@ -99,7 +100,9 @@ class ConductModel:
"""
params = []
# 单班级系统,无需 class_id 过滤
if student_id:
sql += " AND cr.student_id = %s"
params.append(student_id)
if start_date:
sql += " AND DATE(cr.created_at) >= %s"
@@ -114,6 +117,73 @@ class ConductModel:
return await execute_query(sql, tuple(params))
@staticmethod
async def get_grouped_records(
student_id: int = None,
start_date: str = None,
end_date: str = None,
page: int = 1,
page_size: int = 20
) -> Dict[str, Any]:
"""获取分组后的操行分记录(同批次合并)"""
if start_date == "":
start_date = None
if end_date == "":
end_date = None
conditions = ["cr.is_revoked = 0"]
params = []
if student_id:
conditions.append("cr.student_id = %s")
params.append(student_id)
if start_date:
conditions.append("cr.created_at >= %s")
params.append(start_date)
if end_date:
conditions.append("cr.created_at <= %s")
params.append(end_date + ' 23:59:59')
where_clause = " AND ".join(conditions)
count_sql = f"""
SELECT COUNT(DISTINCT CONCAT(cr.points_change, '|', cr.reason, '|', cr.recorder_id, '|', DATE_FORMAT(cr.created_at, '%Y-%m-%d %H:%i'))) as total
FROM conduct_records cr
WHERE {where_clause}
"""
data_sql = f"""
SELECT
cr.points_change,
cr.reason,
cr.recorder_name,
DATE_FORMAT(MIN(cr.created_at), '%Y-%m-%d %H:%i:%s') as created_at,
GROUP_CONCAT(s.name ORDER BY s.student_id SEPARATOR ', ') as student_names,
COUNT(*) as student_count
FROM conduct_records cr
JOIN students s ON cr.student_id = s.student_id
WHERE {where_clause}
GROUP BY cr.points_change, cr.reason, cr.recorder_id, DATE_FORMAT(cr.created_at, '%Y-%m-%d %H:%i')
ORDER BY MIN(cr.created_at) DESC
LIMIT %s OFFSET %s
"""
params_for_count = list(params)
params_for_data = list(params) + [page_size, (page - 1) * page_size]
total_result = await execute_one(count_sql, tuple(params_for_count))
total = total_result['total'] if total_result else 0
records = await execute_query(data_sql, tuple(params_for_data))
return {
"records": records,
"total": total,
"page": page,
"page_size": page_size,
"total_pages": (total + page_size - 1) // page_size
}
@staticmethod
async def get_record_by_id(record_id: int) -> Optional[Dict[str, Any]]:
"""根据ID获取记录"""

View File

@@ -25,7 +25,7 @@ from services.attendance_service import AttendanceService
from services.log_service import LogService
from schemas.admin import (
AddPointsRequest, RevokeRequest, AddAdminRequest,
AddStudentRequest,
AddStudentRequest, UpdateStudentRequest,
UpdateHomeworkStatusRequest, AddAttendanceRequest,
UpdateAdminRequest, DeleteAdminRequest, ResetPasswordRequest
)
@@ -124,6 +124,58 @@ async def add_student(request: Request, req: AddStudentRequest):
return error_response(message=result["message"])
@router.put("/students/{student_id}")
async def update_student(request: Request, student_id: int, req: UpdateStudentRequest):
"""编辑学生信息(班主任)"""
user = await get_current_user(request)
is_teacher = await PermissionChecker.check_is_teacher(user["user_id"])
if not is_teacher:
return error_response(message="仅班主任可编辑学生信息", code=403)
result = await AdminService.update_student(
student_id=student_id,
name=req.name,
parent_phone=req.parent_phone
)
if result["success"]:
return success_response(message=result["message"])
else:
return error_response(message=result["message"])
@router.delete("/students/{student_id}")
async def delete_student(request: Request, student_id: int):
"""删除学生(班主任)"""
user = await get_current_user(request)
is_teacher = await PermissionChecker.check_is_teacher(user["user_id"])
if not is_teacher:
return error_response(message="仅班主任可删除学生", code=403)
result = await AdminService.delete_student(student_id=student_id)
if result["success"]:
return success_response(message=result["message"])
else:
return error_response(message=result["message"])
@router.post("/students/reset-password/{student_id}")
async def reset_student_password(request: Request, student_id: int, req: ResetPasswordRequest):
"""重置学生密码(班主任)"""
user = await get_current_user(request)
is_teacher = await PermissionChecker.check_is_teacher(user["user_id"])
if not is_teacher:
return error_response(message="仅班主任可重置学生密码", code=403)
result = await AdminService.reset_student_password(
student_id=student_id,
new_password=req.new_password
)
if result["success"]:
return success_response(message=result["message"])
else:
return error_response(message=result["message"])
# ========== 操行分管理 ==========
@router.post("/conduct/add")
@@ -186,7 +238,8 @@ async def get_conduct_history(
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=1000),
start_date: Optional[str] = None,
end_date: Optional[str] = None
end_date: Optional[str] = None,
grouped: bool = Query(False)
):
"""获取操行分历史记录"""
try:
@@ -197,7 +250,8 @@ async def get_conduct_history(
page=page,
page_size=page_size,
start_date=start_date,
end_date=end_date
end_date=end_date,
grouped=grouped
)
return success_response(data=result)
except Exception as e:

View File

@@ -102,4 +102,10 @@ class DeleteAdminRequest(BaseModel):
class ResetPasswordRequest(BaseModel):
"""重置密码请求"""
new_password: str = Field(..., min_length=6, max_length=50, description="新密码")
new_password: str = Field(..., min_length=6, max_length=50, description="新密码")
class UpdateStudentRequest(BaseModel):
"""更新学生请求"""
name: Optional[str] = Field(None, min_length=1, max_length=50, description="姓名")
parent_phone: Optional[str] = Field(None, max_length=20, description="家长手机号")

View File

@@ -10,7 +10,7 @@
# ===========================================
from typing import Dict, Any, List, Optional
from utils.database import execute_query, execute_one
from utils.database import execute_query, execute_one, execute_update
from models.user import UserModel
from models.student import StudentModel
from models.admin_role import AdminRoleModel
@@ -245,4 +245,67 @@ class AdminService:
async def get_admins() -> Dict[str, Any]:
"""获取管理员列表"""
admins = await AdminRoleModel.get_all()
return {"admins": admins}
return {"admins": admins}
@staticmethod
async def update_student(student_id: int, name: str = None, parent_phone: str = None) -> Dict[str, Any]:
"""编辑学生信息"""
try:
student = await StudentModel.get_by_id(student_id)
if not student:
return {"success": False, "message": "学生不存在"}
result = await StudentModel.update(student_id, name=name, parent_phone=parent_phone)
if result:
return {"success": True, "message": "学生信息更新成功"}
return {"success": False, "message": "更新失败"}
except Exception as e:
logger.error(f"更新学生信息失败: {e}")
return {"success": False, "message": f"更新失败: {str(e)}"}
@staticmethod
async def delete_student(student_id: int) -> Dict[str, Any]:
"""删除学生(软删除)"""
try:
student = await StudentModel.get_by_id(student_id)
if not student:
return {"success": False, "message": "学生不存在"}
result = await StudentModel.delete(student_id)
if result:
user = await execute_one(
"SELECT user_id FROM users WHERE student_id = %s AND user_type = 'student'",
(student_id,)
)
if user:
await UserModel.update_status(user['user_id'], 0)
return {"success": True, "message": "学生删除成功"}
return {"success": False, "message": "删除失败"}
except Exception as e:
logger.error(f"删除学生失败: {e}")
return {"success": False, "message": f"删除失败: {str(e)}"}
@staticmethod
async def reset_student_password(student_id: int, new_password: str) -> Dict[str, Any]:
"""重置学生密码"""
try:
user = await execute_one(
"SELECT user_id FROM users WHERE student_id = %s AND user_type = 'student'",
(student_id,)
)
if not user:
return {"success": False, "message": "未找到对应的用户账号"}
password_hash = security.sha1_md5_password(new_password)
result = await UserModel.update_password(user['user_id'], password_hash)
if result:
await execute_update(
"UPDATE users SET need_change_password = 1 WHERE user_id = %s",
(user['user_id'],)
)
return {"success": True, "message": "密码重置成功"}
return {"success": False, "message": "密码重置失败"}
except Exception as e:
logger.error(f"重置学生密码失败: {e}")
return {"success": False, "message": f"重置失败: {str(e)}"}

View File

@@ -159,7 +159,8 @@ class ConductService:
page: int = 1,
page_size: int = 20,
start_date: Optional[str] = None,
end_date: Optional[str] = None
end_date: Optional[str] = None,
grouped: bool = False
) -> Dict[str, Any]:
"""获取历史记录"""
# 空字符串转为None
@@ -173,21 +174,43 @@ class ConductService:
# 班主任/班长/志愿委员可查看全班
if role in ["班主任", "班长", "志愿委员"]:
if grouped:
return await ConductModel.get_grouped_records(
student_id=student_id,
start_date=start_date,
end_date=end_date,
page=page,
page_size=page_size
)
records = await ConductModel.get_all_records(
limit=page_size,
offset=offset,
start_date=start_date,
end_date=end_date
end_date=end_date,
student_id=student_id
)
# 获取总数
from utils.database import execute_one
count_sql = """
count_conditions = ["cr.is_revoked = 0"]
count_params = []
if student_id:
count_conditions.append("cr.student_id = %s")
count_params.append(student_id)
if start_date:
count_conditions.append("DATE(cr.created_at) >= %s")
count_params.append(start_date)
if end_date:
count_conditions.append("DATE(cr.created_at) <= %s")
count_params.append(end_date)
count_where = " AND ".join(count_conditions)
count_sql = f"""
SELECT COUNT(*) as total FROM conduct_records cr
JOIN students s ON cr.student_id = s.student_id
WHERE cr.is_revoked = 0
WHERE {count_where}
"""
total_result = await execute_one(count_sql)
total_result = await execute_one(count_sql, tuple(count_params))
total = total_result["total"] if total_result else 0
elif student_id: