|
|
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() |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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") |
|
|
|
|
|
@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) |
|
|
|
|
|
|
|
|
if not admin_class: |
|
|
return jsonify([]) |
|
|
|
|
|
|
|
|
filtered_data = grade_data |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
filtered_data = filtered_data[filtered_data["行政班级"].str.contains(admin_class, na=False)] |
|
|
|
|
|
|
|
|
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("/api/classes") |
|
|
def get_classes(): |
|
|
classes = grade_data["行政班级"].dropna().unique().tolist() |
|
|
return jsonify(sorted(classes)) |
|
|
|
|
|
|
|
|
@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) |
|
|
|
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
@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) |
|
|
|
|
|
|
|
|
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_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_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 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) |
|
|
|
|
|
|