class123 / app.py
ikun520's picture
Update app.py
9e3419a verified
from flask import Flask, jsonify, render_template, request
import pandas as pd
import re
import json
from datetime import datetime, timedelta
app = Flask(__name__)
# 加载教室到校区的映射
try:
with open('./数据表/classroom_to_campus_mapping.json', 'r', encoding='utf-8') as f:
classroom_to_campus_mapping = json.load(f)
print(f"成功加载教室到校区映射,共{len(classroom_to_campus_mapping)}个教室")
except Exception as e:
print(f"加载教室到校区映射失败: {e}")
classroom_to_campus_mapping = {}
# 加载学生过滤名单
try:
with open('./数据表/student_filter_list.json', 'r', encoding='utf-8') as f:
student_filter_data = json.load(f)
allowed_students = set(student_filter_data.get('allowed_students', []))
print(f"成功加载学生过滤名单,共{len(allowed_students)}个学生")
except Exception as e:
print(f"加载学生过滤名单失败: {e}")
allowed_students = set()
# 根据教室名称获取校区的函数
def get_campus_by_classroom(location):
"""
根据教室名称获取校区
"""
if not location:
return "未知校区"
# 移除括号内容,获取纯教室名称
clean_location = re.sub(r'\([^)]*\)', '', location).strip()
# 首先尝试直接匹配
if location in classroom_to_campus_mapping:
return classroom_to_campus_mapping[location]
# 尝试匹配清理后的教室名称
if clean_location in classroom_to_campus_mapping:
return classroom_to_campus_mapping[clean_location]
# 如果映射中没有找到,使用原有的逻辑作为备用
if "仙葫" in location:
return "仙葫校区"
elif "五合" in location:
return "五合校区"
# 默认返回五合校区
return "五合校区"
# 加载学生数据表
student_file_path = r"./数据表/区队-学号-姓名-1.xlsx"
try:
student_data = pd.read_excel(student_file_path)
except Exception as e:
print(f"加载学生数据失败: {e}")
student_data = pd.DataFrame()
# 加载班级课表数据(包含所有年级的课程信息)
grade_file_path = r"./数据表/班级课表20250830202202.xls"
try:
grade_data = pd.read_excel(grade_file_path)
print(f"成功加载课程数据,共{len(grade_data)}条记录")
except Exception as e:
print(f"加载课程数据失败: {e}")
grade_data = pd.DataFrame()
# 加载 Excel 数据 - 教师课程数据
teacher_files = {
"2024-2025学年第一学期": r"./数据表/教学安排表20250829113240.xls",
}
# 加载教室课表数据
classroom_files = {
"五合校区": r"./数据表/全校课表(按教室) 五合校区.xls",
"仙葫校区": r"./数据表/全校课表(按教室) 仙葫校区.xls",
}
# 预加载教师课程数据
teacher_dataframes = {}
for semester, file_path in teacher_files.items():
try:
df = pd.read_excel(file_path)
df["学年学期"] = semester # 添加学年学期字段
teacher_dataframes[semester] = df
except Exception as e:
print(f"加载教师课程数据失败: {e}")
continue
teacher_data = pd.concat(teacher_dataframes.values(), ignore_index=True) if teacher_dataframes else pd.DataFrame()
# 预加载教室课表数据
classroom_dataframes = {}
for campus, file_path in classroom_files.items():
try:
# 读取HTML格式的Excel文件
df = pd.read_html(file_path, encoding='gbk')[0] # 取第一个表格
df["校区"] = campus # 添加校区字段
classroom_dataframes[campus] = df
print(f"成功加载{campus}教室数据,共{len(df)}条记录")
print(f"数据列名: {df.columns.tolist()}")
except Exception as e:
print(f"加载{campus}教室数据失败: {e}")
continue
classroom_data = pd.concat(classroom_dataframes.values(), ignore_index=True) if classroom_dataframes else pd.DataFrame()
print(f"教室数据总计: {len(classroom_data)}条记录")
# 第一周的开始日期
first_week_start_date = datetime(2025, 9, 1) # 第一周星期一的日期(2025年9月1日是周一)
def parse_weeks(weeks_str):
if not weeks_str or pd.isna(weeks_str):
return set()
weeks = set()
for part in weeks_str.split(","):
try:
if "-" in part:
start, end = map(int, part.split("-"))
weeks.update(range(start, end + 1))
else:
weeks.add(int(part))
except ValueError:
print(f"跳过无效周次: {part}")
continue
return weeks
# print(parse_weeks("1-2,4,7-9"))
# 星期与节次解析函数
def parse_day_and_period(period_str):
if not period_str or pd.isna(period_str):
return None
try:
day_match = re.search(r"[一二三四五六日]", period_str)
# 修复正则表达式,同时支持 [数字-数字节] 和 [数字-数字] 两种格式
period_match = re.search(r"\[(\d+)-(\d+)节?\]", period_str)
if day_match and period_match:
day = "一二三四五六日".index(day_match.group()) + 1
start, end = map(int, period_match.groups())
periods = list(range(start, end + 1))
return day, periods
else:
# 移除调试打印,解析失败时静默返回None
pass
except Exception as e:
print(f"解析异常: {period_str}, 错误: {e}")
return None
# 根据周次和星期计算实际日期
def calculate_date(week, day):
days_from_start = (week - 1) * 7 + (day - 1) # 从第一周开始的天数差
return first_week_start_date + timedelta(days=days_from_start)
@app.route("/")
def index():
return render_template("index.html")
@app.route("/teachers")
def teacher_page():
return render_template("teacher.html")
# 学生课程相关 API
@app.route("/api/student_courses")
def get_student_courses():
week = request.args.get("week", 1)
grade = request.args.get("grade", None)
admin_class = request.args.get("admin_class", None)
# 参数验证:如果没有提供admin_class参数,返回空结果
if not admin_class:
return jsonify([])
# 筛选数据
filtered_data = grade_data
# 注意:新的数据文件中没有'grade'列,所以跳过grade筛选
# if grade:
# filtered_data = filtered_data[filtered_data["grade"] == grade]
# 筛选指定班级的数据
filtered_data = filtered_data[filtered_data["行政班级"].str.contains(admin_class, na=False)]
# 如果没有找到匹配的班级,返回空结果
if filtered_data.empty:
return jsonify([])
# 移除课程类型筛选,显示所有课程(必修课、选修课等)
# filtered_data = filtered_data[filtered_data["课程类别"].str.contains("必修课", na=False)]
if week:
week = int(week)
filtered_data = filtered_data[
filtered_data["周次"].apply(lambda x: week in parse_weeks(x) if pd.notna(x) else False)
]
# 解析课程信息
results = []
for _, row in filtered_data.iterrows():
day_and_period = parse_day_and_period(row["节次"])
if day_and_period:
day, periods = day_and_period
course_date = calculate_date(week, day) # 计算课程日期
# 根据地点判断校区
location_str = str(row["地点"]) if pd.notna(row["地点"]) else ""
location = location_str.split("(")[0] if "(" in location_str else location_str
campus = get_campus_by_classroom(location_str)
# 简化校区名称显示
campus_display = campus.replace("校区", "")
results.append({
"课程": row["课程"].split("]")[1].strip() if "]" in row["课程"] and len(row["课程"].split("]")) > 1 and row["课程"].split("]")[1].strip() else row["课程"],
"教师": row["教师"],
"地点": location,
"星期": day,
"日期": course_date.strftime("%Y-%m-%d"), # 格式化为字符串
"节次": periods,
"节次范围": f"第{periods[0]}-{periods[-1]}节",
"周次": row["周次"],
"校区": campus_display,
"上课班级": str(row["行政班级"]) if "行政班级" in row else ""
})
# 按星期和节次排序
results = sorted(results, key=lambda x: (x["星期"], x["节次"][0]))
return jsonify(results)
@app.route("/api/classes")
def get_classes():
classes = grade_data["行政班级"].dropna().unique().tolist()
return jsonify(sorted(classes))
# 教师课程相关 API
@app.route("/api/teachers")
def get_teachers():
teachers = teacher_data["教师"].dropna().unique().tolist()
return jsonify(sorted(teachers))
@app.route("/api/teacher_courses")
def get_courses_by_teacher():
week = request.args.get("week", 1)
teacher = request.args.get("teacher", None)
# 参数验证:如果没有提供teacher参数,返回空结果
if not teacher:
return jsonify([])
# 筛选数据
filtered_data = teacher_data
# 筛选指定教师的数据
filtered_data = filtered_data[filtered_data["教师"] == teacher]
# 如果没有找到匹配的教师,返回空结果
if filtered_data.empty:
return jsonify([])
if week:
week = int(week)
filtered_data = filtered_data[filtered_data["周次"].apply(lambda x: week in parse_weeks(x) if pd.notna(x) else False)]
# 解析课程信息
results = []
for _, row in filtered_data.iterrows():
day_and_period = parse_day_and_period(row["节次"])
if day_and_period:
day, periods = day_and_period
course_date = calculate_date(week, day) # 计算课程日期
# 根据地点判断校区
location_str = str(row["地点"]) if pd.notna(row["地点"]) else ""
location = location_str.split("(")[0] if "(" in location_str else location_str
campus = get_campus_by_classroom(location_str)
# 简化校区名称显示
campus_display = campus.replace("校区", "")
results.append({
"课程": row["课程"].split("]")[1].strip() if "]" in row["课程"] and len(row["课程"].split("]")) > 1 and row["课程"].split("]")[1].strip() else row["课程"],
"教师": row["教师"],
"地点": location,
"星期": day,
"日期": course_date.strftime("%Y-%m-%d"), # 格式化为字符串
"节次": periods,
"节次范围": f"第{periods[0]}-{periods[-1]}节",
"周次": row["周次"],
"校区": campus_display,
"上课班级": str(row["行政班级"]) if "行政班级" in row else ""
})
# 按星期和节次排序
results = sorted(results, key=lambda x: (x["星期"], x["节次"][0]))
return jsonify(results)
# 路由:学生查询页面
@app.route("/students")
def student_page():
return render_template("student.html")
@app.route("/classrooms")
def classroom_page():
return render_template("classroom.html")
@app.route("/schedule-overlap")
def schedule_overlap_page():
return render_template("schedule_overlap.html")
@app.route("/api/students")
def get_students():
students = student_data["姓名"].dropna().unique().tolist()
return jsonify(sorted(students))
# 教室相关 API
@app.route("/api/campuses")
def get_campuses():
if classroom_data.empty:
return jsonify([])
campuses = classroom_data["校区"].dropna().unique().tolist()
return jsonify(sorted(campuses))
@app.route("/api/classrooms")
def get_classrooms():
campus = request.args.get("campus", None)
print(f"请求校区: {campus}")
print(f"classroom_data是否为空: {classroom_data.empty}")
if classroom_data.empty:
print("教室数据为空,返回空列表")
return jsonify([])
filtered_data = classroom_data
if campus:
filtered_data = filtered_data[filtered_data["校区"] == campus]
print(f"筛选后的数据条数: {len(filtered_data)}")
classrooms = filtered_data["教室"].dropna().unique().tolist()
print(f"找到的教室数量: {len(classrooms)}")
print(f"前5个教室: {classrooms[:5] if classrooms else '无'}")
return jsonify(sorted(classrooms))
@app.route("/api/classroom_courses")
def get_courses_by_classroom():
week = request.args.get("week", 1)
classroom = request.args.get("classroom", None)
campus = request.args.get("campus", None)
# 参数验证:如果没有提供classroom参数,返回空结果
if not classroom:
return jsonify([])
if classroom_data.empty:
return jsonify({"error": "教室数据未加载"}), 500
# 筛选数据
filtered_data = classroom_data
if campus:
filtered_data = filtered_data[filtered_data["校区"] == campus]
# 筛选指定教室的数据
filtered_data = filtered_data[filtered_data["教室"] == classroom]
# 如果没有找到匹配的教室,返回空结果
if filtered_data.empty:
return jsonify([])
if week:
week = int(week)
filtered_data = filtered_data[
filtered_data["周次"].apply(lambda x: week in parse_weeks(str(x)) if pd.notna(x) else False)
]
# 解析课程信息
results = []
seen_courses = set() # 用于去重的集合
for _, row in filtered_data.iterrows():
day_and_period = parse_day_and_period(str(row["节次"]))
if day_and_period:
day, periods = day_and_period
course_date = calculate_date(week, day)
# 提取课程名称
raw_course_name = str(row["课程名称"])
if "]" in raw_course_name:
parts = raw_course_name.split("]")
if len(parts) > 1 and parts[1].strip():
course_name = parts[1].strip()
else:
course_name = raw_course_name # 如果分割后为空,使用原始名称
else:
course_name = raw_course_name
# 如果课程名称为空或只有空白字符,跳过这条记录
if not course_name or course_name.strip() == "" or course_name == "nan":
continue
# 创建唯一标识符:星期+节次+课程名称
course_key = (day, tuple(periods), course_name)
# 如果这个课程时间段组合已经存在,跳过
if course_key in seen_courses:
continue
seen_courses.add(course_key)
results.append({
"课程": course_name,
"教师": str(row["教师"]),
"地点": str(row["教室"]),
"星期": day,
"日期": course_date.strftime("%Y-%m-%d"),
"节次": periods,
"节次范围": f"第{periods[0]}-{periods[-1]}节",
"周次": str(row["周次"]),
"校区": str(row["校区"]),
"上课班级": str(row["行政班级"]) if "行政班级" in row else ""
})
# 按星期和节次排序
results = sorted(results, key=lambda x: (x["星期"], x["节次"][0]))
return jsonify(results)
@app.route("/api/schedule_overlap")
def get_schedule_overlap():
"""
获取23级大数据1区、24级大数据1/2/3区的课表叠加数据
"""
week = request.args.get("week", "1")
# 目标班级列表
target_classes = ["23大数据1区", "24大数据1区", "24大数据2区", "24大数据3区", "24信息安全技术应用1区"]
try:
# 解析周次
week_num = int(week)
# 获取所有目标班级的课程数据
all_courses = []
for class_name in target_classes:
# 筛选指定班级和周次的课程
class_courses = grade_data[
(grade_data["行政班级"] == class_name) &
(grade_data["周次"].apply(lambda x: week_num in parse_weeks(str(x)) if pd.notna(x) else False))
]
for _, course in class_courses.iterrows():
# 解析节次和星期
day_and_period = parse_day_and_period(course["节次"])
if day_and_period:
day, periods = day_and_period
# 计算日期
course_date = calculate_date(week_num, day)
course_info = {
"班级": class_name,
"课程": course["课程"],
"教师": course["教师"],
"地点": course["地点"],
"校区": get_campus_by_classroom(course["地点"]),
"日期": course_date.strftime("%Y-%m-%d"),
"星期": day,
"节次": periods,
"周次": course["周次"] # 添加周次信息,便于前端区分
}
all_courses.append(course_info)
return jsonify(all_courses)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/class_students")
def get_class_students():
"""
获取指定班级的学生名单
"""
class_name = request.args.get("class_name", "")
if not class_name:
return jsonify({"error": "缺少班级名称参数"}), 400
try:
# 使用拷贝的数据,避免修改原始 student_data
student_data_copy = student_data.copy()
student_data_copy["区队"] = student_data_copy["区队"].str.extract(r"\](.*)$")[0].str.strip()
# 查找指定班级的学生
class_students = student_data_copy[student_data_copy["区队"] == class_name]
# 获取学生名单并过滤
all_students = class_students["姓名"].tolist()
# 只返回在过滤名单中的学生
filtered_students = [student for student in all_students if student in allowed_students]
return jsonify(filtered_students)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route("/api/student_courses_v2")
def get_student_courses_v2():
week = request.args.get("week", 1)
student_name = request.args.get("student_name", "").strip()
if not student_name:
return jsonify({"error": "缺少学生姓名参数"}), 400
# 使用拷贝的数据,避免修改原始 student_data
student_data_copy = student_data.copy()
student_data_copy["区队"] = student_data_copy["区队"].str.extract(r"\](.*)$")[0].str.strip()
# 查找匹配的学生信息
matching_students = student_data_copy[student_data_copy["姓名"].str.contains(student_name, na=False)]
if matching_students.empty:
return jsonify([]) # 返回空数组而不是错误对象
# 获取学生所在班级
admin_classes = matching_students["区队"].unique()
# 筛选课程数据
if '行政班级' in grade_data.columns:
filtered_data = grade_data[grade_data["行政班级"].isin(admin_classes)]
else:
return jsonify([])
# 移除课程类型筛选,显示所有课程(必修课、选修课等)
# if '课程类别' in filtered_data.columns:
# filtered_data = filtered_data[filtered_data["课程类别"].str.contains("必修课", na=False)]
# 按周次筛选
if week:
week = int(week)
if '周次' in filtered_data.columns:
filtered_data = filtered_data[
filtered_data["周次"].apply(lambda x: week in parse_weeks(x) if pd.notna(x) else False)
]
# 如果没有找到课程数据
if filtered_data.empty:
return jsonify([]) # 返回空数组而不是错误对象
# 解析课程信息
results = []
for _, row in filtered_data.iterrows():
day_and_period = parse_day_and_period(row["节次"])
if day_and_period:
day, periods = day_and_period
course_date = calculate_date(week, day) # 计算课程日期
# 根据地点判断校区
location_str = str(row["地点"]) if pd.notna(row["地点"]) else ""
location = location_str.split("(")[0] if "(" in location_str else location_str
campus = get_campus_by_classroom(location_str)
# 简化校区名称显示
campus_display = campus.replace("校区", "")
results.append({
"课程": row["课程"].split("]")[1].strip() if "]" in row["课程"] and len(row["课程"].split("]")) > 1 and row["课程"].split("]")[1].strip() else row["课程"],
"教师": row["教师"],
"地点": location,
"星期": day,
"日期": course_date.strftime("%Y-%m-%d"), # 格式化为字符串
"节次": periods,
"节次范围": f"第{periods[0]}-{periods[-1]}节",
"周次": row["周次"],
"校区": campus_display,
"上课班级": str(row["行政班级"]) if "行政班级" in row else ""
})
# 按星期和节次排序
results = sorted(results, key=lambda x: (x["星期"], x["节次"][0]))
return jsonify(results)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860, debug=False)