ikun520 commited on
Commit
8db211b
·
verified ·
1 Parent(s): 60931b5

Upload 5 files

Browse files
Files changed (5) hide show
  1. app.py +397 -0
  2. templates/classroom.html +1203 -0
  3. templates/index.html +1074 -0
  4. templates/student.html +1100 -0
  5. templates/teacher.html +712 -0
app.py ADDED
@@ -0,0 +1,397 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, jsonify, render_template, request
2
+ import pandas as pd
3
+ import re
4
+ from datetime import datetime, timedelta
5
+
6
+ app = Flask(__name__)
7
+
8
+ # 加载学生数据表
9
+ student_file_path = r"./数据表/区队-学号-姓名-1.xlsx"
10
+ try:
11
+ student_data = pd.read_excel(student_file_path)
12
+ except Exception as e:
13
+ print(f"加载学生数据失败: {e}")
14
+ student_data = pd.DataFrame()
15
+
16
+ # 加载班级课表数据(包含所有年级的课程信息)
17
+ grade_file_path = r"./数据表/班级课表20250830202202.xls"
18
+ try:
19
+ grade_data = pd.read_excel(grade_file_path)
20
+ print(f"成功加载课程数据,共{len(grade_data)}条记录")
21
+ except Exception as e:
22
+ print(f"加载课程数据失败: {e}")
23
+ grade_data = pd.DataFrame()
24
+
25
+ # 加载 Excel 数据 - 教师课程数据
26
+ teacher_files = {
27
+ "2024-2025学年第一学期": r"./数据表/教学安排表20250829113240.xls",
28
+ }
29
+
30
+ # 加载教室课表数据
31
+ classroom_files = {
32
+ "五合校区": r"./数据表/全校课表(按教室) 五合校区.xls",
33
+ "仙葫校区": r"./数据表/全校课表(按教室) 仙葫校区.xls",
34
+ }
35
+
36
+ # 预加载教师课程数据
37
+ teacher_dataframes = {}
38
+ for semester, file_path in teacher_files.items():
39
+ try:
40
+ df = pd.read_excel(file_path)
41
+ df["学年学期"] = semester # 添加学年学期字段
42
+ teacher_dataframes[semester] = df
43
+ except Exception as e:
44
+ print(f"加载教师课程数据失败: {e}")
45
+ continue
46
+
47
+ teacher_data = pd.concat(teacher_dataframes.values(), ignore_index=True) if teacher_dataframes else pd.DataFrame()
48
+
49
+ # 预加载教室课表数据
50
+ classroom_dataframes = {}
51
+ for campus, file_path in classroom_files.items():
52
+ try:
53
+ # 读取HTML格式的Excel文件
54
+ df = pd.read_html(file_path, encoding='gbk')[0] # 取第一个表格
55
+ df["校区"] = campus # 添加校区字段
56
+ classroom_dataframes[campus] = df
57
+ print(f"成功加载{campus}教室数据,共{len(df)}条记录")
58
+ print(f"数据列名: {df.columns.tolist()}")
59
+ except Exception as e:
60
+ print(f"加载{campus}教室数据失败: {e}")
61
+ continue
62
+
63
+ classroom_data = pd.concat(classroom_dataframes.values(), ignore_index=True) if classroom_dataframes else pd.DataFrame()
64
+ print(f"教室数据总计: {len(classroom_data)}条记录")
65
+
66
+ # 第一周的开始日期
67
+ first_week_start_date = datetime(2024, 9, 2) # 第一周星期一的日期
68
+
69
+
70
+
71
+ def parse_weeks(weeks_str):
72
+ if not weeks_str or pd.isna(weeks_str):
73
+ return set()
74
+ weeks = set()
75
+ for part in weeks_str.split(","):
76
+ try:
77
+ if "-" in part:
78
+ start, end = map(int, part.split("-"))
79
+ weeks.update(range(start, end + 1))
80
+ else:
81
+ weeks.add(int(part))
82
+ except ValueError:
83
+ print(f"跳过无效周次: {part}")
84
+ continue
85
+ return weeks
86
+
87
+
88
+ # print(parse_weeks("1-2,4,7-9"))
89
+ # 星期与节次解析函数
90
+ def parse_day_and_period(period_str):
91
+ if not period_str or pd.isna(period_str):
92
+ return None
93
+ try:
94
+ day_match = re.search(r"[一二三四五六日]", period_str)
95
+ # 修复正则表达式,同时支持 [数字-数字节] 和 [数字-数字] 两种格式
96
+ period_match = re.search(r"\[(\d+)-(\d+)节?\]", period_str)
97
+
98
+ if day_match and period_match:
99
+ day = "一二三四五六日".index(day_match.group()) + 1
100
+ start, end = map(int, period_match.groups())
101
+ periods = list(range(start, end + 1))
102
+ return day, periods
103
+ else:
104
+ # 移除调试打印,解析失败时静默返回None
105
+ pass
106
+ except Exception as e:
107
+ print(f"解析异常: {period_str}, 错误: {e}")
108
+ return None
109
+
110
+ # 根据周次和星期计算实际日期
111
+ def calculate_date(week, day):
112
+ days_from_start = (week - 1) * 7 + (day - 1) # 从第一周开始的天数差
113
+ return first_week_start_date + timedelta(days=days_from_start)
114
+
115
+ @app.route("/")
116
+ def index():
117
+ return render_template("index.html")
118
+ @app.route("/teachers")
119
+ def teacher_page():
120
+ return render_template("teacher.html")
121
+ # 学生课程相关 API
122
+ @app.route("/api/student_courses")
123
+ def get_student_courses():
124
+ week = request.args.get("week", 1)
125
+ grade = request.args.get("grade", None)
126
+ admin_class = request.args.get("admin_class", None)
127
+
128
+ # 筛选数据
129
+ filtered_data = grade_data
130
+
131
+ # 注意:新的数据文件中没有'grade'列,所以跳过grade筛选
132
+ # if grade:
133
+ # filtered_data = filtered_data[filtered_data["grade"] == grade]
134
+
135
+ if admin_class:
136
+ filtered_data = filtered_data[filtered_data["行政班级"].str.contains(admin_class, na=False)]
137
+
138
+ # 筛选课程类型:只要必修课
139
+ filtered_data = filtered_data[filtered_data["课程类别"].str.contains("必修课", na=False)]
140
+
141
+ if week:
142
+ week = int(week)
143
+ filtered_data = filtered_data[
144
+ filtered_data["周次"].apply(lambda x: week in parse_weeks(x) if pd.notna(x) else False)
145
+ ]
146
+
147
+ # 解析课程信息
148
+ results = []
149
+ for _, row in filtered_data.iterrows():
150
+ day_and_period = parse_day_and_period(row["节次"])
151
+ if day_and_period:
152
+ day, periods = day_and_period
153
+ course_date = calculate_date(week, day) # 计算课程日期
154
+
155
+ # 根据地点判断校区
156
+ location = row["地点"].split("(")[0] if "(" in row["地点"] else row["地点"]
157
+ campus = "仙葫" if "仙葫" in str(row["地点"]) else "五合"
158
+
159
+ results.append({
160
+ "课程": row["课程"].split("]")[1] if "]" in row["课程"] else row["课程"],
161
+ "教师": row["教师"],
162
+ "地点": location,
163
+ "星期": day,
164
+ "日期": course_date.strftime("%Y-%m-%d"), # 格式化为字符串
165
+ "节次": periods,
166
+ "节次范围": f"第{periods[0]}-{periods[-1]}节",
167
+ "周次": row["周次"],
168
+ "校区": campus,
169
+ "上课班级": str(row["行政班级"]) if "行政班级" in row else ""
170
+ })
171
+
172
+ # 按星期和节次排序
173
+ results = sorted(results, key=lambda x: (x["星期"], x["节次"][0]))
174
+ return jsonify(results)
175
+
176
+ @app.route("/api/classes")
177
+ def get_classes():
178
+ classes = grade_data["行政班级"].dropna().unique().tolist()
179
+ return jsonify(sorted(classes))
180
+
181
+ # 教师课程相关 API
182
+ @app.route("/api/teachers")
183
+ def get_teachers():
184
+ teachers = teacher_data["教师"].dropna().unique().tolist()
185
+ return jsonify(sorted(teachers))
186
+
187
+ @app.route("/api/teacher_courses")
188
+ def get_courses_by_teacher():
189
+ week = request.args.get("week", 1)
190
+ teacher = request.args.get("teacher", None)
191
+
192
+ # 筛选数据
193
+ filtered_data = teacher_data
194
+ if teacher:
195
+ filtered_data = filtered_data[filtered_data["教师"] == teacher]
196
+ if week:
197
+ week = int(week)
198
+ filtered_data = filtered_data[filtered_data["周次"].apply(lambda x: week in parse_weeks(x) if pd.notna(x) else False)]
199
+
200
+ # 解析课程信息
201
+ results = []
202
+ for _, row in filtered_data.iterrows():
203
+ day_and_period = parse_day_and_period(row["节次"])
204
+ if day_and_period:
205
+ day, periods = day_and_period
206
+ course_date = calculate_date(week, day) # 计算课程日期
207
+
208
+ # 根据地点判断校区
209
+ location = row["地点"].split("(")[0] if "(" in row["地点"] else row["地点"]
210
+ campus = "仙葫" if "仙葫" in str(row["地点"]) else "五合"
211
+
212
+ results.append({
213
+ "课程": row["课程"].split("]")[1] if "]" in row["课程"] else row["课程"],
214
+ "教师": row["教师"],
215
+ "地点": location,
216
+ "星期": day,
217
+ "日期": course_date.strftime("%Y-%m-%d"), # 格式化为字符串
218
+ "节次": periods,
219
+ "节次范围": f"第{periods[0]}-{periods[-1]}节",
220
+ "周次": row["周次"],
221
+ "校区": campus,
222
+ "上课班级": str(row["行政班级"]) if "行政班级" in row else ""
223
+ })
224
+
225
+ # 按星期和节次排序
226
+ results = sorted(results, key=lambda x: (x["星期"], x["节次"][0]))
227
+ return jsonify(results)
228
+
229
+ # 路由:学生查询页面
230
+ @app.route("/students")
231
+ def student_page():
232
+ return render_template("student.html")
233
+
234
+ @app.route("/classrooms")
235
+ def classroom_page():
236
+ return render_template("classroom.html")
237
+
238
+ @app.route("/api/students")
239
+ def get_students():
240
+ students = student_data["姓名"].dropna().unique().tolist()
241
+ return jsonify(sorted(students))
242
+
243
+ # 教室相关 API
244
+ @app.route("/api/campuses")
245
+ def get_campuses():
246
+ if classroom_data.empty:
247
+ return jsonify([])
248
+ campuses = classroom_data["校区"].dropna().unique().tolist()
249
+ return jsonify(sorted(campuses))
250
+
251
+ @app.route("/api/classrooms")
252
+ def get_classrooms():
253
+ campus = request.args.get("campus", None)
254
+ print(f"请求校区: {campus}")
255
+ print(f"classroom_data是否为空: {classroom_data.empty}")
256
+
257
+ if classroom_data.empty:
258
+ print("教室数据为空,返回空列表")
259
+ return jsonify([])
260
+
261
+ filtered_data = classroom_data
262
+ if campus:
263
+ filtered_data = filtered_data[filtered_data["校区"] == campus]
264
+ print(f"筛选后的数据条数: {len(filtered_data)}")
265
+
266
+ classrooms = filtered_data["教室"].dropna().unique().tolist()
267
+ print(f"找到的教室数量: {len(classrooms)}")
268
+ print(f"前5个教室: {classrooms[:5] if classrooms else '无'}")
269
+ return jsonify(sorted(classrooms))
270
+
271
+ @app.route("/api/classroom_courses")
272
+ def get_courses_by_classroom():
273
+ week = request.args.get("week", 1)
274
+ classroom = request.args.get("classroom", None)
275
+ campus = request.args.get("campus", None)
276
+
277
+
278
+
279
+ if classroom_data.empty:
280
+ return jsonify({"error": "教室数据未加载"}), 500
281
+
282
+ # 筛选数据
283
+ filtered_data = classroom_data
284
+
285
+ if campus:
286
+ filtered_data = filtered_data[filtered_data["校区"] == campus]
287
+ if classroom:
288
+ filtered_data = filtered_data[filtered_data["教室"] == classroom]
289
+
290
+ if week:
291
+ week = int(week)
292
+ filtered_data = filtered_data[
293
+ filtered_data["周次"].apply(lambda x: week in parse_weeks(str(x)) if pd.notna(x) else False)
294
+ ]
295
+
296
+ # 解析课程信息
297
+ results = []
298
+ for _, row in filtered_data.iterrows():
299
+ day_and_period = parse_day_and_period(str(row["节次"]))
300
+ if day_and_period:
301
+ day, periods = day_and_period
302
+ course_date = calculate_date(week, day)
303
+ results.append({
304
+ "课程": str(row["课程名称"]).split("]")[1] if "]" in str(row["课程名称"]) else str(row["课程名称"]),
305
+ "教师": str(row["教师"]),
306
+ "地点": str(row["教室"]),
307
+ "星期": day,
308
+ "日期": course_date.strftime("%Y-%m-%d"),
309
+ "节次": periods,
310
+ "节次范围": f"第{periods[0]}-{periods[-1]}节",
311
+ "周次": str(row["周次"]),
312
+ "校区": str(row["校区"]),
313
+ "上课班级": str(row["行政班级"]) if "行政班级" in row else ""
314
+ })
315
+
316
+ # 按星期和节次排序
317
+ results = sorted(results, key=lambda x: (x["星期"], x["节次"][0]))
318
+ return jsonify(results)
319
+
320
+ @app.route("/api/student_courses_v2")
321
+ def get_student_courses_v2():
322
+ week = request.args.get("week", 1)
323
+ student_name = request.args.get("student_name", "").strip()
324
+
325
+ if not student_name:
326
+ return jsonify({"error": "缺少学生姓名参数"}), 400
327
+
328
+ # 使用拷贝的数据,避免修改原始 student_data
329
+ student_data_copy = student_data.copy()
330
+ student_data_copy["区队"] = student_data_copy["区队"].str.extract(r"\](.*)$")[0].str.strip()
331
+
332
+ # 查找匹配的学生信息
333
+ matching_students = student_data_copy[student_data_copy["姓名"].str.contains(student_name, na=False)]
334
+
335
+ if matching_students.empty:
336
+ return jsonify([]) # 返回空数组而不是错误对象
337
+
338
+ # 获取学生所在班级
339
+ admin_classes = matching_students["区队"].unique()
340
+
341
+ # 筛选课程数据
342
+ if '行政班级' in grade_data.columns:
343
+ filtered_data = grade_data[grade_data["行政班级"].isin(admin_classes)]
344
+ else:
345
+ return jsonify([])
346
+
347
+ # 按课程类型筛选
348
+ if '课程类别' in filtered_data.columns:
349
+ filtered_data = filtered_data[filtered_data["课程类别"].str.contains("必修课", na=False)]
350
+
351
+ # 按周次筛选
352
+ if week:
353
+ week = int(week)
354
+ if '周次' in filtered_data.columns:
355
+ filtered_data = filtered_data[
356
+ filtered_data["周次"].apply(lambda x: week in parse_weeks(x) if pd.notna(x) else False)
357
+ ]
358
+
359
+ # 如果没有找到课程数据
360
+ if filtered_data.empty:
361
+ return jsonify([]) # 返回空数组而不是错误对象
362
+
363
+ # 解析课程信息
364
+ results = []
365
+ for _, row in filtered_data.iterrows():
366
+ day_and_period = parse_day_and_period(row["节次"])
367
+ if day_and_period:
368
+ day, periods = day_and_period
369
+ course_date = calculate_date(week, day) # 计算课程日期
370
+
371
+ # 根据地点判断校区
372
+ location = row["地点"].split("(")[0] if "(" in row["地点"] else row["地点"]
373
+ campus = "仙葫" if "仙葫" in str(row["地点"]) else "五合"
374
+
375
+ results.append({
376
+ "课程": row["课程"].split("]")[1] if "]" in row["课程"] else row["课程"],
377
+ "教师": row["教师"],
378
+ "地点": location,
379
+ "星期": day,
380
+ "日期": course_date.strftime("%Y-%m-%d"), # 格式化为字符串
381
+ "节次": periods,
382
+ "节次范围": f"第{periods[0]}-{periods[-1]}节",
383
+ "周次": row["周次"],
384
+ "校区": campus,
385
+ "上课班级": str(row["行政班级"]) if "行政班级" in row else ""
386
+ })
387
+
388
+ # 按星期和节次排序
389
+ results = sorted(results, key=lambda x: (x["星期"], x["节次"][0]))
390
+ return jsonify(results)
391
+
392
+
393
+
394
+
395
+ if __name__ == "__main__":
396
+ app.run(host="0.0.0.0", port=7860, debug=False)
397
+
templates/classroom.html ADDED
@@ -0,0 +1,1203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
+ <title>教室课表</title>
7
+ <style>
8
+ /* 全局样式 */
9
+ body {
10
+ font-family: 'Roboto', Arial, sans-serif;
11
+ margin: 0;
12
+ padding: 0;
13
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 25%, #f093fb 50%, #f5576c 75%, #4facfe 100%);
14
+ background-size: 400% 400%;
15
+ animation: gradientShift 15s ease infinite;
16
+ color: #333;
17
+ position: relative;
18
+ overflow-x: hidden;
19
+ }
20
+
21
+ /* 背景动画 */
22
+ @keyframes gradientShift {
23
+ 0% { background-position: 0% 50%; }
24
+ 50% { background-position: 100% 50%; }
25
+ 100% { background-position: 0% 50%; }
26
+ }
27
+
28
+ /* 磨砂玻璃背景层 */
29
+ body::before {
30
+ content: '';
31
+ position: fixed;
32
+ top: 0;
33
+ left: 0;
34
+ width: 100%;
35
+ height: 100%;
36
+ background: rgba(255, 255, 255, 0.1);
37
+ backdrop-filter: blur(10px);
38
+ z-index: -1;
39
+ pointer-events: none;
40
+ }
41
+
42
+ /* 浮动粒子效果 */
43
+ body::after {
44
+ content: '';
45
+ position: fixed;
46
+ top: 0;
47
+ left: 0;
48
+ width: 100%;
49
+ height: 100%;
50
+ background-image:
51
+ radial-gradient(circle at 20% 80%, rgba(255, 255, 255, 0.1) 2px, transparent 2px),
52
+ radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.15) 1px, transparent 1px),
53
+ radial-gradient(circle at 40% 40%, rgba(255, 255, 255, 0.08) 3px, transparent 3px);
54
+ background-size: 200px 200px, 150px 150px, 300px 300px;
55
+ animation: particleFloat 20s linear infinite;
56
+ z-index: -1;
57
+ pointer-events: none;
58
+ }
59
+
60
+ @keyframes particleFloat {
61
+ 0% { transform: translateY(0px) rotate(0deg); }
62
+ 100% { transform: translateY(-100vh) rotate(360deg); }
63
+ }
64
+ h1 {
65
+ text-align: center;
66
+ margin: 20px 0;
67
+ font-size: 28px;
68
+ font-weight: bold;
69
+ color: #0078d4;
70
+ letter-spacing: 1.2px;
71
+ }
72
+ /* 控制区域样式 */
73
+ .controls {
74
+ display: flex;
75
+ justify-content: space-between;
76
+ align-items: center;
77
+ padding: 20px;
78
+ background: rgba(255, 255, 255, 0.3);
79
+ backdrop-filter: blur(20px);
80
+ color: #0078d4;
81
+ border-radius: 15px;
82
+ border: 1px solid rgba(255, 255, 255, 0.2);
83
+ margin: 20px;
84
+ box-shadow:
85
+ 0 8px 32px rgba(0, 0, 0, 0.1),
86
+ 0 2px 8px rgba(0, 0, 0, 0.05),
87
+ inset 0 1px 0 rgba(255, 255, 255, 0.3);
88
+ position: relative;
89
+ z-index: 10;
90
+ }
91
+
92
+ .controls::before {
93
+ content: '';
94
+ position: absolute;
95
+ top: 0;
96
+ left: 0;
97
+ right: 0;
98
+ bottom: 0;
99
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
100
+ border-radius: 15px;
101
+ z-index: -1;
102
+ animation: controlsShimmer 3s ease-in-out infinite;
103
+ }
104
+
105
+ @keyframes controlsShimmer {
106
+ 0%, 100% { opacity: 0.5; }
107
+ 50% { opacity: 0.8; }
108
+ }
109
+ .controls div {
110
+ flex: 1;
111
+ text-align: center;
112
+ }
113
+ .controls label {
114
+ font-weight: bold;
115
+ margin-right: 10px;
116
+ }
117
+ .controls select,
118
+ .controls button {
119
+ padding: 12px 24px;
120
+ font-size: 16px;
121
+ border: 1px solid rgba(255, 255, 255, 0.3);
122
+ border-radius: 8px;
123
+ cursor: pointer;
124
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
125
+ backdrop-filter: blur(10px);
126
+ position: relative;
127
+ overflow: hidden;
128
+ }
129
+
130
+ .controls button {
131
+ background: linear-gradient(135deg, rgba(0, 120, 212, 0.8) 0%, rgba(0, 90, 158, 0.9) 100%);
132
+ color: white;
133
+ box-shadow:
134
+ 0 4px 15px rgba(0, 120, 212, 0.3),
135
+ inset 0 1px 0 rgba(255, 255, 255, 0.2);
136
+ }
137
+
138
+ .controls button::before {
139
+ content: '';
140
+ position: absolute;
141
+ top: 0;
142
+ left: -100%;
143
+ width: 100%;
144
+ height: 100%;
145
+ background: linear-gradient(90deg, transparent, rgba(255, 215, 0, 0.4), transparent);
146
+ transition: left 0.5s;
147
+ }
148
+
149
+ .controls select {
150
+ background: rgba(255, 255, 255, 0.9);
151
+ color: #0078d4;
152
+ font-weight: bold;
153
+ box-shadow:
154
+ 0 2px 10px rgba(0, 0, 0, 0.1),
155
+ inset 0 1px 0 rgba(255, 255, 255, 0.5);
156
+ }
157
+
158
+ .controls select:hover,
159
+ .controls button:hover {
160
+ transform: translateY(-2px);
161
+ }
162
+
163
+ .controls button:hover {
164
+ background: linear-gradient(135deg, rgba(0, 90, 158, 0.9) 0%, rgba(0, 120, 212, 1) 100%);
165
+ box-shadow:
166
+ 0 8px 25px rgba(0, 120, 212, 0.4),
167
+ inset 0 1px 0 rgba(255, 255, 255, 0.3);
168
+ }
169
+
170
+ .controls button:hover::before {
171
+ left: 100%;
172
+ }
173
+
174
+ .controls select:hover {
175
+ background: rgba(255, 255, 255, 1);
176
+ box-shadow:
177
+ 0 4px 15px rgba(0, 0, 0, 0.15),
178
+ inset 0 1px 0 rgba(255, 255, 255, 0.7);
179
+ }
180
+ .controls select:focus,
181
+ .controls button:focus {
182
+ outline: none;
183
+ box-shadow: 0 0 5px rgba(0, 120, 212, 0.8);
184
+ }
185
+ /* 导航按钮样式 */
186
+ .nav-button {
187
+ padding: 10px 15px;
188
+ font-size: 14px;
189
+ border: none;
190
+ border-radius: 5px;
191
+ background-color: #fff;
192
+ color: #0078d4;
193
+ font-weight: bold;
194
+ cursor: pointer;
195
+ transition: all 0.3s ease;
196
+ margin: 0 5px;
197
+ }
198
+ .nav-button:hover {
199
+ background-color: #eaf4fc;
200
+ }
201
+ .nav-button:focus {
202
+ outline: none;
203
+ box-shadow: 0 0 5px rgba(0, 120, 212, 0.8);
204
+ }
205
+ /* 时间表头样式 */
206
+ .time-header {
207
+ display: grid;
208
+ grid-template-columns: 70px repeat(7, 1fr);
209
+ background: rgba(255, 255, 255, 0.3);
210
+ backdrop-filter: blur(20px);
211
+ color: #0078d4;
212
+ text-align: center;
213
+ margin: 20px;
214
+ font-size: 14px;
215
+ font-weight: bold;
216
+ padding: 12px 0;
217
+ border-radius: 12px;
218
+ border: 1px solid rgba(255, 255, 255, 0.2);
219
+ box-shadow:
220
+ 0 8px 32px rgba(0, 0, 0, 0.1),
221
+ 0 2px 8px rgba(0, 0, 0, 0.05),
222
+ inset 0 1px 0 rgba(255, 255, 255, 0.3),
223
+ inset 0 -1px 0 rgba(0, 0, 0, 0.1);
224
+ position: relative;
225
+ overflow: hidden;
226
+ }
227
+
228
+ .time-header::before {
229
+ content: '';
230
+ position: absolute;
231
+ top: 0;
232
+ left: -100%;
233
+ width: 100%;
234
+ height: 100%;
235
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
236
+ animation: headerShimmer 4s ease-in-out infinite;
237
+ }
238
+
239
+ @keyframes headerShimmer {
240
+ 0% { left: -100%; }
241
+ 50% { left: 100%; }
242
+ 100% { left: 100%; }
243
+ }
244
+ .time-header div {
245
+ border-right: 1px solid rgba(255, 255, 255, 0.2);
246
+ padding: 8px;
247
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
248
+ position: relative;
249
+ z-index: 1;
250
+ }
251
+ .time-header div:last-child {
252
+ border-right: none;
253
+ }
254
+ .time-header div:hover {
255
+ transform: translateY(-3px) scale(1.02);
256
+ background: rgba(255, 255, 255, 0.4);
257
+ backdrop-filter: blur(15px);
258
+ cursor: pointer;
259
+ border-radius: 8px;
260
+ box-shadow:
261
+ 0 8px 25px rgba(0, 120, 212, 0.2),
262
+ 0 4px 12px rgba(0, 0, 0, 0.1),
263
+ inset 0 1px 0 rgba(255, 255, 255, 0.5);
264
+ }
265
+ /* 课程表网格样式 */
266
+ .schedule-grid {
267
+ display: grid;
268
+ grid-template-columns: 70px repeat(7, 1fr);
269
+ gap: 2px;
270
+ margin: 20px;
271
+ background: rgba(255, 255, 255, 0.3);
272
+ backdrop-filter: blur(20px);
273
+ border: 1px solid rgba(255, 255, 255, 0.2);
274
+ border-radius: 15px;
275
+ overflow: hidden;
276
+ box-shadow:
277
+ 0 8px 32px rgba(0, 0, 0, 0.1),
278
+ 0 2px 8px rgba(0, 0, 0, 0.05),
279
+ inset 0 1px 0 rgba(255, 255, 255, 0.3);
280
+ position: relative;
281
+ }
282
+
283
+ .schedule-grid::before {
284
+ content: '';
285
+ position: absolute;
286
+ top: 0;
287
+ left: 0;
288
+ right: 0;
289
+ bottom: 0;
290
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
291
+ pointer-events: none;
292
+ }
293
+ /* 时间列样式 */
294
+ .time-column {
295
+ background-color: #f8f9fa;
296
+ text-align: center;
297
+ font-size: 14px;
298
+ font-weight: bold;
299
+ padding: 20px 5px;
300
+ border-right: 1px solid #ddd;
301
+ display: flex;
302
+ align-items: center;
303
+ justify-content: center;
304
+ color: #0078d4;
305
+ }
306
+ /* 网格单元格样式 */
307
+ .grid-cell {
308
+ background: rgba(255, 255, 255, 0.4);
309
+ backdrop-filter: blur(10px);
310
+ border: 1px solid rgba(255, 255, 255, 0.3);
311
+ min-height: 120px;
312
+ padding: 8px;
313
+ position: relative;
314
+ border-radius: 8px;
315
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
316
+ box-shadow:
317
+ 0 2px 10px rgba(0, 0, 0, 0.05),
318
+ inset 0 1px 0 rgba(255, 255, 255, 0.4);
319
+ }
320
+ .grid-cell:hover {
321
+ transform: translateY(-3px) scale(1.01);
322
+ background: rgba(255, 255, 255, 0.5);
323
+ box-shadow:
324
+ 0 8px 25px rgba(0, 0, 0, 0.1),
325
+ 0 4px 12px rgba(0, 0, 0, 0.05),
326
+ inset 0 1px 0 rgba(255, 255, 255, 0.5);
327
+ }
328
+ /* 课程样式 */
329
+ .course {
330
+ background: linear-gradient(135deg, rgba(167, 216, 222, 0.9) 0%, rgba(128, 199, 205, 0.8) 100%);
331
+ backdrop-filter: blur(10px);
332
+ color: #2c3e50;
333
+ border: 1px solid rgba(255, 255, 255, 0.3);
334
+ border-radius: 10px;
335
+ padding: 10px;
336
+ font-size: 14px;
337
+ margin: 3px;
338
+ box-shadow:
339
+ 0 6px 20px rgba(0, 0, 0, 0.1),
340
+ 0 2px 8px rgba(0, 0, 0, 0.05),
341
+ inset 0 1px 0 rgba(255, 255, 255, 0.4),
342
+ inset 0 -1px 0 rgba(0, 0, 0, 0.1);
343
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
344
+ border-left: 4px solid rgba(0, 120, 212, 0.8);
345
+ position: relative;
346
+ overflow: hidden;
347
+ }
348
+
349
+ .course::before {
350
+ content: '';
351
+ position: absolute;
352
+ top: 0;
353
+ left: -100%;
354
+ width: 100%;
355
+ height: 100%;
356
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
357
+ transition: left 0.6s ease;
358
+ }
359
+
360
+ .course:hover {
361
+ transform: scale(1.05) translateY(-2px);
362
+ background: linear-gradient(135deg, rgba(167, 216, 222, 1) 0%, rgba(128, 199, 205, 0.95) 100%);
363
+ box-shadow:
364
+ 0 12px 30px rgba(0, 0, 0, 0.15),
365
+ 0 4px 15px rgba(0, 0, 0, 0.1),
366
+ inset 0 1px 0 rgba(255, 255, 255, 0.5),
367
+ inset 0 -1px 0 rgba(0, 0, 0, 0.1);
368
+ }
369
+
370
+ .course:hover::before {
371
+ left: 100%;
372
+ }
373
+ .course strong {
374
+ display: block;
375
+ font-size: 15px;
376
+ margin-bottom: 4px;
377
+ color: #0078d4;
378
+ }
379
+ .course .teacher,
380
+ .course .class,
381
+ .course .period {
382
+ font-size: 13px;
383
+ color: #666;
384
+ margin: 2px 0;
385
+ }
386
+ /* 无课程提示 */
387
+ .no-courses {
388
+ grid-column: 1 / -1;
389
+ text-align: center;
390
+ padding: 40px;
391
+ color: #999;
392
+ font-size: 16px;
393
+ background-color: #f8f9fa;
394
+ border-radius: 8px;
395
+ margin: 20px;
396
+ }
397
+ /* 周次选择器样式 */
398
+ .week-selector {
399
+ display: flex;
400
+ align-items: center;
401
+ gap: 10px;
402
+ }
403
+ /* 合并课程样式 */
404
+ .course {
405
+ cursor: pointer;
406
+ }
407
+
408
+ /* 课程指示器样式 */
409
+ .course-indicator {
410
+ background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
411
+ color: #333;
412
+ border-radius: 8px;
413
+ padding: 15px 8px;
414
+ font-size: 14px;
415
+ text-align: center;
416
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
417
+ transition: transform 0.3s ease, background-color 0.3s ease;
418
+ border-left: 4px solid #2196f3;
419
+ cursor: pointer;
420
+ height: 100%;
421
+ display: flex;
422
+ flex-direction: column;
423
+ justify-content: center;
424
+ align-items: center;
425
+ }
426
+
427
+ .course-indicator:hover {
428
+ transform: scale(1.02);
429
+ background: linear-gradient(135deg, #bbdefb 0%, #90caf9 100%);
430
+ }
431
+
432
+ .course-indicator .has-course {
433
+ font-weight: bold;
434
+ color: #1976d2;
435
+ margin-bottom: 5px;
436
+ font-size: 15px;
437
+ }
438
+
439
+ .course-indicator .course-count {
440
+ font-size: 13px;
441
+ color: #666;
442
+ }
443
+
444
+ .course-summary {
445
+ background: linear-gradient(135deg, #a7d8de 0%, #80c7cd 100%);
446
+ color: #333;
447
+ border-radius: 8px;
448
+ padding: 8px;
449
+ font-size: 14px;
450
+ margin: 2px;
451
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
452
+ transition: transform 0.3s ease, background-color 0.3s ease;
453
+ border-left: 4px solid #0078d4;
454
+ cursor: pointer;
455
+ }
456
+ .course-summary:hover {
457
+ transform: scale(1.02);
458
+ background: linear-gradient(135deg, #80c7cd 0%, #5fb3ba 100%);
459
+ }
460
+ .course-summary .course-count {
461
+ font-size: 12px;
462
+ color: #0078d4;
463
+ font-weight: bold;
464
+ margin-bottom: 2px;
465
+ }
466
+ .course-summary .course-list {
467
+ font-size: 13px;
468
+ line-height: 1.3;
469
+ }
470
+ .course-summary .teacher-list {
471
+ font-size: 12px;
472
+ color: #666;
473
+ margin-top: 4px;
474
+ }
475
+ /* 模态框样式 */
476
+ .modal {
477
+ display: none;
478
+ position: fixed;
479
+ z-index: 1000;
480
+ left: 0;
481
+ top: 0;
482
+ width: 100%;
483
+ height: 100%;
484
+ background-color: rgba(0, 0, 0, 0.5);
485
+ }
486
+ .modal-content {
487
+ background-color: #fff;
488
+ margin: 10% auto;
489
+ padding: 20px;
490
+ border-radius: 10px;
491
+ width: 80%;
492
+ max-width: 500px;
493
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
494
+ }
495
+ .modal-header {
496
+ display: flex;
497
+ justify-content: space-between;
498
+ align-items: center;
499
+ margin-bottom: 15px;
500
+ padding-bottom: 10px;
501
+ border-bottom: 2px solid #0078d4;
502
+ }
503
+ .modal-title {
504
+ color: #0078d4;
505
+ font-size: 18px;
506
+ font-weight: bold;
507
+ }
508
+ .close {
509
+ color: #999;
510
+ font-size: 24px;
511
+ font-weight: bold;
512
+ cursor: pointer;
513
+ transition: color 0.3s;
514
+ }
515
+ .close:hover {
516
+ color: #0078d4;
517
+ }
518
+ .course-detail {
519
+ margin: 10px 0;
520
+ padding: 10px;
521
+ background-color: #f8f9fa;
522
+ border-radius: 5px;
523
+ border-left: 4px solid #0078d4;
524
+ }
525
+ .course-detail .course-name {
526
+ font-weight: bold;
527
+ color: #0078d4;
528
+ margin-bottom: 5px;
529
+ }
530
+ .course-detail .course-info {
531
+ font-size: 14px;
532
+ color: #666;
533
+ margin: 2px 0;
534
+ }
535
+ /* 移动端适配 */
536
+ @media (max-width: 768px) {
537
+ body {
538
+ font-size: 14px;
539
+ padding: 5px;
540
+ }
541
+ h1 {
542
+ font-size: 20px;
543
+ margin: 15px 0;
544
+ }
545
+ .controls {
546
+ display: flex;
547
+ flex-direction: column;
548
+ gap: 10px;
549
+ padding: 10px;
550
+ margin: 5px;
551
+ }
552
+ .controls div {
553
+ flex: none;
554
+ text-align: center;
555
+ }
556
+ .controls select,
557
+ .controls button,
558
+ .controls input {
559
+ padding: 8px;
560
+ font-size: 12px;
561
+ margin: 2px;
562
+ min-width: 80px;
563
+ }
564
+
565
+ .classroom-search {
566
+ width: 100%;
567
+ padding: 8px 12px !important;
568
+ font-size: 12px !important;
569
+ border: 1px solid #0078d4;
570
+ border-radius: 5px;
571
+ color: #333;
572
+ box-sizing: border-box;
573
+ }
574
+
575
+ .search-results {
576
+ position: absolute;
577
+ top: calc(100% + 2px);
578
+ left: 0;
579
+ width: 100%;
580
+ background: #f0f8ff;
581
+ border: 1px solid #0078d4;
582
+ max-height: 150px;
583
+ overflow-y: auto;
584
+ z-index: 1000;
585
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
586
+ border-radius: 5px;
587
+ box-sizing: border-box;
588
+ }
589
+ .nav-button {
590
+ padding: 8px 12px;
591
+ font-size: 12px;
592
+ margin: 2px;
593
+ }
594
+ .time-header {
595
+ grid-template-columns: 50px repeat(7, 1fr);
596
+ font-size: 12px;
597
+ padding: 8px 0;
598
+ margin: 5px;
599
+ }
600
+ .schedule-grid {
601
+ grid-template-columns: 50px repeat(7, minmax(0, 1fr));
602
+ margin: 5px;
603
+ gap: 1px;
604
+ }
605
+ .time-column {
606
+ font-size: 12px;
607
+ padding: 15px 2px;
608
+ }
609
+ .grid-cell {
610
+ min-height: 100px;
611
+ padding: 3px;
612
+ }
613
+ .course {
614
+ font-size: 10px;
615
+ padding: 4px;
616
+ margin: 1px;
617
+ }
618
+ .course strong {
619
+ font-size: 11px;
620
+ }
621
+ .course .teacher,
622
+ .course .class,
623
+ .course .period {
624
+ font-size: 11px;
625
+ }
626
+ /* 移动端模态��样式 */
627
+ .modal-content {
628
+ width: 95%;
629
+ margin: 5% auto;
630
+ padding: 15px;
631
+ }
632
+ .modal-title {
633
+ font-size: 18px;
634
+ }
635
+ .course-detail {
636
+ margin: 8px 0;
637
+ padding: 8px;
638
+ }
639
+ .course-detail .course-name {
640
+ font-size: 16px;
641
+ }
642
+ .course-detail .course-info {
643
+ font-size: 14px;
644
+ }
645
+ .course-summary .course-count {
646
+ font-size: 11px;
647
+ }
648
+ .course-summary .course-list {
649
+ font-size: 12px;
650
+ }
651
+ .course-summary .teacher-list {
652
+ font-size: 11px;
653
+ }
654
+ }
655
+
656
+ /* 搜索结果样式 */
657
+ .search-results {
658
+ background: #f0f8ff;
659
+ border: 1px solid #0078d4;
660
+ border-radius: 5px;
661
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
662
+ }
663
+
664
+ .search-results div {
665
+ padding: 10px 15px;
666
+ cursor: pointer;
667
+ border-bottom: 1px solid #e0e0e0;
668
+ transition: background-color 0.2s ease;
669
+ }
670
+
671
+ .search-results div:last-child {
672
+ border-bottom: none;
673
+ }
674
+
675
+ .search-results div:hover {
676
+ background-color: #e3f2fd;
677
+ color: #0078d4;
678
+ }
679
+
680
+ .classroom-search:focus {
681
+ outline: none;
682
+ box-shadow: 0 0 5px rgba(0, 120, 212, 0.8);
683
+ }
684
+
685
+ .classroom-search:hover {
686
+ background: rgba(255, 255, 255, 1);
687
+ transform: translateY(-1px);
688
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.7);
689
+ }
690
+ </style>
691
+ </head>
692
+ <body>
693
+ <h1>教室课表查询</h1>
694
+
695
+ <div class="controls">
696
+ <div>
697
+ <button class="nav-button" onclick="location.href='/'">班级查询</button>
698
+ <button class="nav-button" onclick="location.href='/students'">学生查询</button>
699
+ <button class="nav-button" onclick="location.href='/teachers'">教师查询</button>
700
+ </div>
701
+
702
+ <div>
703
+ <label>校区:</label>
704
+ <button id="campus-btn" class="nav-button" onclick="toggleCampus()">仙葫校区</button>
705
+ </div>
706
+
707
+ <div class="search-container" style="position: relative;">
708
+ <label for="classroom-search">教室:</label>
709
+ <input type="text" id="classroom-search" class="classroom-search" placeholder="搜索教室名称" style="padding: 12px 24px; font-size: 14px; border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 8px; background: rgba(255, 255, 255, 0.9); color: #0078d4; font-weight: bold; cursor: text; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); backdrop-filter: blur(10px); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.5);">
710
+ <div id="classroom-search-results" class="search-results" style="display: none; position: absolute; top: calc(100% + 2px); left: 0; width: 100%; background: #f0f8ff; border: 1px solid #0078d4; max-height: 150px; overflow-y: auto; z-index: 1000; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); border-radius: 5px; box-sizing: border-box;"></div>
711
+ </div>
712
+
713
+ <div class="week-selector">
714
+ <label for="week">周次:</label>
715
+ <select id="week" onchange="changeWeekBySelect()">
716
+ <option value="1">第1周</option>
717
+ <option value="2">第2周</option>
718
+ <option value="3">第3周</option>
719
+ <option value="4">第4周</option>
720
+ <option value="5">第5周</option>
721
+ <option value="6">第6周</option>
722
+ <option value="7">第7周</option>
723
+ <option value="8">第8周</option>
724
+ <option value="9">第9周</option>
725
+ <option value="10">第10周</option>
726
+ <option value="11">第11周</option>
727
+ <option value="12">第12周</option>
728
+ <option value="13">第13周</option>
729
+ <option value="14">第14周</option>
730
+ <option value="15">第15周</option>
731
+ <option value="16">第16周</option>
732
+ <option value="17">第17周</option>
733
+ <option value="18">第18周</option>
734
+ <option value="19">第19周</option>
735
+ <option value="20">第20周</option>
736
+ </select>
737
+ </div>
738
+ </div>
739
+
740
+ <div class="time-header">
741
+ <div>节次</div>
742
+ <div>周一</div>
743
+ <div>周二</div>
744
+ <div>周三</div>
745
+ <div>周四</div>
746
+ <div>周五</div>
747
+ <div>周六</div>
748
+ <div>周日</div>
749
+ </div>
750
+
751
+ <div class="schedule-grid" id="schedule-container">
752
+ <!-- 课程表将通过JavaScript动态生成 -->
753
+ <!-- 示���结构 -->
754
+ <div class="time-column">第1-3节</div>
755
+ <div class="grid-cell"></div>
756
+ <div class="grid-cell"></div>
757
+ <div class="grid-cell"></div>
758
+ <div class="grid-cell"></div>
759
+ <div class="grid-cell"></div>
760
+ <div class="grid-cell"></div>
761
+ <div class="grid-cell"></div>
762
+
763
+ <div class="time-column">第4-5节</div>
764
+ <div class="grid-cell"></div>
765
+ <div class="grid-cell"></div>
766
+ <div class="grid-cell"></div>
767
+ <div class="grid-cell"></div>
768
+ <div class="grid-cell"></div>
769
+ <div class="grid-cell"></div>
770
+ <div class="grid-cell"></div>
771
+
772
+ <div class="time-column">第6-8节</div>
773
+ <div class="grid-cell"></div>
774
+ <div class="grid-cell"></div>
775
+ <div class="grid-cell"></div>
776
+ <div class="grid-cell"></div>
777
+ <div class="grid-cell"></div>
778
+ <div class="grid-cell"></div>
779
+ <div class="grid-cell"></div>
780
+
781
+ <div class="time-column">第9-11节</div>
782
+ <div class="grid-cell"></div>
783
+ <div class="grid-cell"></div>
784
+ <div class="grid-cell"></div>
785
+ <div class="grid-cell"></div>
786
+ <div class="grid-cell"></div>
787
+ <div class="grid-cell"></div>
788
+ <div class="grid-cell"></div>
789
+ </div>
790
+
791
+ <!-- 课程详情模态框 -->
792
+ <div id="courseModal" class="modal">
793
+ <div class="modal-content">
794
+ <div class="modal-header">
795
+ <div class="modal-title">课程详情</div>
796
+ <span class="close" onclick="closeModal()">&times;</span>
797
+ </div>
798
+ <div id="courseDetails">
799
+ <!-- 课程详情将在这里动态生成 -->
800
+ </div>
801
+ </div>
802
+ </div>
803
+
804
+ <script>
805
+ let currentWeek = 1;
806
+ let classroomList = [];
807
+
808
+ // 页面加载完成后初始化
809
+ window.onload = function() {
810
+ loadClassrooms(); // 先加载教室列表
811
+ loadSchedule();
812
+ };
813
+
814
+ // 搜索教室功能
815
+ function searchClassrooms() {
816
+ const searchInput = document.getElementById("classroom-search").value.toLowerCase();
817
+ const searchResults = document.getElementById("classroom-search-results");
818
+ searchResults.innerHTML = "";
819
+
820
+ if (searchInput.trim() === "") {
821
+ searchResults.style.display = "none";
822
+ return;
823
+ }
824
+
825
+ const filteredClassrooms = classroomList.filter(classroom =>
826
+ classroom.toLowerCase().includes(searchInput)
827
+ );
828
+
829
+ if (filteredClassrooms.length > 0) {
830
+ searchResults.style.display = "block";
831
+ filteredClassrooms.forEach(classroom => {
832
+ const resultDiv = document.createElement("div");
833
+ resultDiv.textContent = classroom;
834
+ resultDiv.onclick = () => selectClassroom(classroom);
835
+ searchResults.appendChild(resultDiv);
836
+ });
837
+ } else {
838
+ searchResults.style.display = "none";
839
+ }
840
+ }
841
+
842
+ // 选择教室
843
+ function selectClassroom(classroom) {
844
+ document.getElementById("classroom-search").value = classroom;
845
+ document.getElementById("classroom-search-results").style.display = "none";
846
+ loadSchedule();
847
+ }
848
+
849
+ // 点击其他地方隐藏搜索结果
850
+ document.addEventListener('click', function(event) {
851
+ const searchContainer = document.querySelector('.search-container');
852
+ const searchResults = document.getElementById('classroom-search-results');
853
+ if (!searchContainer.contains(event.target)) {
854
+ searchResults.style.display = 'none';
855
+ }
856
+ });
857
+
858
+ // 切换校区
859
+ function toggleCampus() {
860
+ const campusBtn = document.getElementById('campus-btn');
861
+ const currentCampus = campusBtn.textContent;
862
+
863
+ if (currentCampus === '仙葫校区') {
864
+ campusBtn.textContent = '五合校区';
865
+ } else {
866
+ campusBtn.textContent = '仙葫校区';
867
+ }
868
+
869
+ loadClassrooms();
870
+ }
871
+
872
+ // 根据校区获取教室列表
873
+ function loadClassrooms() {
874
+ const campus = document.getElementById('campus-btn').textContent;
875
+ const classroomSearch = document.getElementById('classroom-search');
876
+
877
+ // 清空教室搜索框
878
+ classroomSearch.value = '';
879
+ classroomList = [];
880
+
881
+ if (!campus) {
882
+ loadSchedule();
883
+ return;
884
+ }
885
+
886
+ fetch(`/api/classrooms?campus=${encodeURIComponent(campus)}`)
887
+ .then(response => response.json())
888
+ .then(classrooms => {
889
+ classroomList = classrooms.sort(); // 按字母顺序排序
890
+ // 添加搜索事件监听器
891
+ classroomSearch.addEventListener('input', searchClassrooms);
892
+ })
893
+ .catch(error => {
894
+ console.error('获取教室列表失败:', error);
895
+ });
896
+
897
+ // 重新加载课程表
898
+ loadSchedule();
899
+ }
900
+
901
+ // 加载课程表
902
+ function loadSchedule() {
903
+ const campus = document.getElementById('campus-btn').textContent;
904
+ const classroom = document.getElementById('classroom-search').value;
905
+
906
+ if (!classroom) {
907
+ clearSchedule();
908
+ return;
909
+ }
910
+
911
+ let url = `/api/classroom_courses?week=${currentWeek}&classroom=${encodeURIComponent(classroom)}`;
912
+ if (campus) {
913
+ url += `&campus=${encodeURIComponent(campus)}`;
914
+ }
915
+
916
+ fetch(url)
917
+ .then(response => response.json())
918
+ .then(courses => {
919
+ renderSchedule(courses);
920
+ })
921
+ .catch(error => {
922
+ console.error('获取课程表失败:', error);
923
+ });
924
+ }
925
+
926
+ // 渲染课程表
927
+ function renderSchedule(courses) {
928
+ // 清空现有课程
929
+ clearSchedule();
930
+
931
+ if (courses.length === 0) {
932
+ // 如果没有课程,显示提示信息
933
+ document.getElementById('schedule-container').innerHTML = '<div class="no-courses">本周该教室没有安排课程</div>';
934
+ return;
935
+ }
936
+
937
+ // 定义时间槽配置
938
+ const timeSlots = [
939
+ { id: 1, start: 1, end: 3, label: '第1-3节' },
940
+ { id: 2, start: 4, end: 5, label: '第4-5节' },
941
+ { id: 3, start: 6, end: 8, label: '第6-8节' },
942
+ { id: 4, start: 9, end: 11, label: '第9-11节' }
943
+ ];
944
+
945
+ // 将课程按星期和时间槽分组
946
+ const schedule = {};
947
+ courses.forEach(course => {
948
+ const day = course.星期;
949
+ const periods = course.节次;
950
+
951
+ if (!schedule[day]) {
952
+ schedule[day] = {};
953
+ }
954
+
955
+ // 确定课程属于哪个时间槽 - 修复逻辑:课程必须完全在时间槽范围内
956
+ const firstPeriod = periods[0];
957
+ const lastPeriod = periods[periods.length - 1];
958
+
959
+ // 查找匹配的时间槽 - 课程必须完全包含在时间槽内
960
+ const timeSlot = timeSlots.find(slot =>
961
+ firstPeriod >= slot.start && lastPeriod <= slot.end
962
+ );
963
+
964
+ // 将课程添加到对应时间槽
965
+ if (timeSlot) {
966
+ if (!schedule[day][timeSlot.id]) {
967
+ schedule[day][timeSlot.id] = [];
968
+ }
969
+ // 添加节次范围信息到课程对象中
970
+ const courseWithPeriod = {...course, 节次范围: `第${firstPeriod}-${lastPeriod}节`};
971
+ schedule[day][timeSlot.id].push(courseWithPeriod);
972
+ }
973
+ });
974
+
975
+ // 填充课程到表格中
976
+ const container = document.getElementById('schedule-container');
977
+ const cells = container.querySelectorAll('.grid-cell');
978
+
979
+ cells.forEach((cell, index) => {
980
+ const day = (index % 7) + 1; // 星期一到星期日为1-7
981
+ const timeSlotId = Math.floor(index / 7) + 1; // 时间槽 1-4
982
+
983
+ // 查找该时段的课程
984
+ if (schedule[day] && schedule[day][timeSlotId]) {
985
+ const courses = schedule[day][timeSlotId];
986
+
987
+ // 简化显示,只显示有课程的标识
988
+ const courseElement = document.createElement('div');
989
+ courseElement.className = 'course-indicator';
990
+ courseElement.innerHTML = `
991
+ <div class="has-course">有课程</div>
992
+ <div class="course-count">${courses.length}门</div>
993
+ `;
994
+ courseElement.onclick = () => showCourseDetails(courses);
995
+ cell.appendChild(courseElement);
996
+ }
997
+ });
998
+ }
999
+
1000
+ // 清空课程表
1001
+ function clearSchedule() {
1002
+ const container = document.getElementById('schedule-container');
1003
+
1004
+ // 恢复初始结构
1005
+ container.innerHTML = `
1006
+ <div class="time-column">第1-3节</div>
1007
+ <div class="grid-cell"></div>
1008
+ <div class="grid-cell"></div>
1009
+ <div class="grid-cell"></div>
1010
+ <div class="grid-cell"></div>
1011
+ <div class="grid-cell"></div>
1012
+ <div class="grid-cell"></div>
1013
+ <div class="grid-cell"></div>
1014
+
1015
+ <div class="time-column">第4-5节</div>
1016
+ <div class="grid-cell"></div>
1017
+ <div class="grid-cell"></div>
1018
+ <div class="grid-cell"></div>
1019
+ <div class="grid-cell"></div>
1020
+ <div class="grid-cell"></div>
1021
+ <div class="grid-cell"></div>
1022
+ <div class="grid-cell"></div>
1023
+
1024
+ <div class="time-column">第6-8节</div>
1025
+ <div class="grid-cell"></div>
1026
+ <div class="grid-cell"></div>
1027
+ <div class="grid-cell"></div>
1028
+ <div class="grid-cell"></div>
1029
+ <div class="grid-cell"></div>
1030
+ <div class="grid-cell"></div>
1031
+ <div class="grid-cell"></div>
1032
+
1033
+ <div class="time-column">第9-11节</div>
1034
+ <div class="grid-cell"></div>
1035
+ <div class="grid-cell"></div>
1036
+ <div class="grid-cell"></div>
1037
+ <div class="grid-cell"></div>
1038
+ <div class="grid-cell"></div>
1039
+ <div class="grid-cell"></div>
1040
+ <div class="grid-cell"></div>
1041
+ `;
1042
+ }
1043
+
1044
+ // 通过下拉选择器更改周次
1045
+ function changeWeekBySelect() {
1046
+ const weekSelect = document.getElementById('week');
1047
+ currentWeek = parseInt(weekSelect.value);
1048
+ loadSchedule();
1049
+ }
1050
+
1051
+ // 更改周次(保留原函数以防其他地方调用)
1052
+ function changeWeek(delta) {
1053
+ currentWeek += delta;
1054
+ if (currentWeek < 1) currentWeek = 1;
1055
+ if (currentWeek > 20) currentWeek = 20;
1056
+ document.getElementById('week').value = currentWeek;
1057
+ loadSchedule();
1058
+ }
1059
+
1060
+ // 显示课程详情模态框
1061
+ function showCourseDetails(courses) {
1062
+ const modal = document.getElementById('courseModal');
1063
+ const detailsContainer = document.getElementById('courseDetails');
1064
+
1065
+ // 清空之前的内容
1066
+ detailsContainer.innerHTML = '';
1067
+
1068
+ // 为每个课程创建详情卡片
1069
+ courses.forEach(course => {
1070
+ const courseDetail = document.createElement('div');
1071
+ courseDetail.className = 'course-detail';
1072
+ courseDetail.innerHTML = `
1073
+ <div class="course-name">${course.课程}</div>
1074
+ <div class="course-info">教师: ${course.教师}</div>
1075
+ <div class="course-info">班级: ${course.上课班级}</div>
1076
+ <div class="course-info">时间: ${course.节次范围}</div>
1077
+ `;
1078
+ detailsContainer.appendChild(courseDetail);
1079
+ });
1080
+
1081
+ // 显示模态框
1082
+ modal.style.display = 'block';
1083
+ }
1084
+
1085
+ // 关闭模态框
1086
+ function closeModal() {
1087
+ const modal = document.getElementById('courseModal');
1088
+ modal.style.display = 'none';
1089
+ }
1090
+
1091
+ // 点击模态框外部关闭
1092
+ window.onclick = function(event) {
1093
+ const modal = document.getElementById('courseModal');
1094
+ if (event.target === modal) {
1095
+ modal.style.display = 'none';
1096
+ }
1097
+ }
1098
+
1099
+ // 鼠标轨迹粒子效果
1100
+ class MouseTrailParticle {
1101
+ constructor(x, y) {
1102
+ this.x = x;
1103
+ this.y = y;
1104
+ this.vx = (Math.random() - 0.5) * 4;
1105
+ this.vy = (Math.random() - 0.5) * 4;
1106
+ this.life = 1.0;
1107
+ this.decay = 0.02 + Math.random() * 0.02;
1108
+ this.size = 2 + Math.random() * 4;
1109
+ this.color = `hsl(${Math.random() * 60 + 200}, 70%, 70%)`;
1110
+ }
1111
+
1112
+ update() {
1113
+ this.x += this.vx;
1114
+ this.y += this.vy;
1115
+ this.vx *= 0.98;
1116
+ this.vy *= 0.98;
1117
+ this.life -= this.decay;
1118
+ this.size *= 0.98;
1119
+ }
1120
+
1121
+ draw(ctx) {
1122
+ ctx.save();
1123
+ ctx.globalAlpha = this.life;
1124
+ ctx.fillStyle = this.color;
1125
+ ctx.shadowBlur = 10;
1126
+ ctx.shadowColor = this.color;
1127
+ ctx.beginPath();
1128
+ ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
1129
+ ctx.fill();
1130
+ ctx.restore();
1131
+ }
1132
+
1133
+ isDead() {
1134
+ return this.life <= 0 || this.size <= 0.1;
1135
+ }
1136
+ }
1137
+
1138
+ // 创建画布
1139
+ const canvas = document.createElement('canvas');
1140
+ canvas.style.position = 'fixed';
1141
+ canvas.style.top = '0';
1142
+ canvas.style.left = '0';
1143
+ canvas.style.width = '100%';
1144
+ canvas.style.height = '100%';
1145
+ canvas.style.pointerEvents = 'none';
1146
+ canvas.style.zIndex = '9999';
1147
+ document.body.appendChild(canvas);
1148
+
1149
+ const ctx = canvas.getContext('2d');
1150
+ const particles = [];
1151
+ let mouseX = 0;
1152
+ let mouseY = 0;
1153
+ let lastMouseX = 0;
1154
+ let lastMouseY = 0;
1155
+
1156
+ function resizeCanvas() {
1157
+ canvas.width = window.innerWidth;
1158
+ canvas.height = window.innerHeight;
1159
+ }
1160
+
1161
+ function addParticles(x, y) {
1162
+ const distance = Math.sqrt((x - lastMouseX) ** 2 + (y - lastMouseY) ** 2);
1163
+ if (distance > 5) {
1164
+ for (let i = 0; i < 3; i++) {
1165
+ particles.push(new MouseTrailParticle(x + (Math.random() - 0.5) * 10, y + (Math.random() - 0.5) * 10));
1166
+ }
1167
+ lastMouseX = x;
1168
+ lastMouseY = y;
1169
+ }
1170
+ }
1171
+
1172
+ function animate() {
1173
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1174
+
1175
+ // 更新和绘制粒子
1176
+ for (let i = particles.length - 1; i >= 0; i--) {
1177
+ const particle = particles[i];
1178
+ particle.update();
1179
+ particle.draw(ctx);
1180
+
1181
+ if (particle.isDead()) {
1182
+ particles.splice(i, 1);
1183
+ }
1184
+ }
1185
+
1186
+ requestAnimationFrame(animate);
1187
+ }
1188
+
1189
+ // 事件监听
1190
+ document.addEventListener('mousemove', (e) => {
1191
+ mouseX = e.clientX;
1192
+ mouseY = e.clientY;
1193
+ addParticles(mouseX, mouseY);
1194
+ });
1195
+
1196
+ window.addEventListener('resize', resizeCanvas);
1197
+
1198
+ // 初始化
1199
+ resizeCanvas();
1200
+ animate();
1201
+ </script>
1202
+ </body>
1203
+ </html>
templates/index.html ADDED
@@ -0,0 +1,1074 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <!-- 适配不同设备 -->
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
8
+ <title>课程表</title>
9
+ <style>
10
+ /* 全局样式 */
11
+ body {
12
+ font-family: 'Roboto', Arial, sans-serif;
13
+ margin: 0;
14
+ padding: 0;
15
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 25%, #f093fb 50%, #f5576c 75%, #4facfe 100%);
16
+ background-size: 400% 400%;
17
+ animation: gradientShift 15s ease infinite;
18
+ color: #333;
19
+ position: relative;
20
+ overflow-x: hidden;
21
+ }
22
+
23
+ /* 背景动画 */
24
+ @keyframes gradientShift {
25
+ 0% { background-position: 0% 50%; }
26
+ 50% { background-position: 100% 50%; }
27
+ 100% { background-position: 0% 50%; }
28
+ }
29
+
30
+ /* 磨砂玻璃背景层 */
31
+ body::before {
32
+ content: '';
33
+ position: fixed;
34
+ top: 0;
35
+ left: 0;
36
+ width: 100%;
37
+ height: 100%;
38
+ background: rgba(255, 255, 255, 0.1);
39
+ backdrop-filter: blur(10px);
40
+ z-index: -1;
41
+ pointer-events: none;
42
+ }
43
+
44
+ /* 浮动粒子效果 */
45
+ body::after {
46
+ content: '';
47
+ position: fixed;
48
+ top: 0;
49
+ left: 0;
50
+ width: 100%;
51
+ height: 100%;
52
+ background-image:
53
+ radial-gradient(circle at 20% 80%, rgba(255, 255, 255, 0.1) 2px, transparent 2px),
54
+ radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.15) 1px, transparent 1px),
55
+ radial-gradient(circle at 40% 40%, rgba(255, 255, 255, 0.08) 3px, transparent 3px);
56
+ background-size: 200px 200px, 150px 150px, 300px 300px;
57
+ animation: particleFloat 20s linear infinite;
58
+ z-index: -1;
59
+ pointer-events: none;
60
+ }
61
+
62
+ @keyframes particleFloat {
63
+ 0% { transform: translateY(0px) rotate(0deg); }
64
+ 100% { transform: translateY(-100vh) rotate(360deg); }
65
+ }
66
+ h1 {
67
+ text-align: center;
68
+ margin: 20px 0;
69
+ font-size: 28px;
70
+ font-weight: bold;
71
+ color: #fff;
72
+ letter-spacing: 1.2px;
73
+ text-shadow: 0 0 20px rgba(255, 255, 255, 0.5), 0 0 40px rgba(255, 255, 255, 0.3);
74
+ animation: titleGlow 3s ease-in-out infinite alternate;
75
+ }
76
+
77
+ @keyframes titleGlow {
78
+ 0% { text-shadow: 0 0 20px rgba(255, 255, 255, 0.5), 0 0 40px rgba(255, 255, 255, 0.3); }
79
+ 100% { text-shadow: 0 0 30px rgba(255, 255, 255, 0.8), 0 0 60px rgba(255, 255, 255, 0.5); }
80
+ }
81
+ /* 控制区域样式 */
82
+ .controls {
83
+ display: flex;
84
+ justify-content: space-between;
85
+ align-items: center;
86
+ padding: 15px 20px;
87
+ background: rgba(255, 255, 255, 0.15);
88
+ backdrop-filter: blur(20px);
89
+ border: 1px solid rgba(255, 255, 255, 0.2);
90
+ color: white;
91
+ border-radius: 15px;
92
+ margin: 10px 20px;
93
+ box-shadow:
94
+ 0 8px 32px rgba(0, 0, 0, 0.1),
95
+ inset 0 1px 0 rgba(255, 255, 255, 0.2),
96
+ 0 1px 0 rgba(255, 255, 255, 0.1);
97
+ position: relative;
98
+ overflow: hidden;
99
+ }
100
+
101
+ .controls::before {
102
+ content: '';
103
+ position: absolute;
104
+ top: 0;
105
+ left: -100%;
106
+ width: 100%;
107
+ height: 100%;
108
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
109
+ animation: shimmer 3s infinite;
110
+ }
111
+
112
+ @keyframes shimmer {
113
+ 0% { left: -100%; }
114
+ 100% { left: 100%; }
115
+ }
116
+ .controls div {
117
+ flex: 1; /* 保证每个div等宽 */
118
+ text-align: center; /* 中心对齐 */
119
+ }
120
+ .controls label {
121
+ font-weight: bold;
122
+ margin-right: 10px;
123
+ }
124
+ .controls select,
125
+ .controls button {
126
+ padding: 8px 12px;
127
+ font-size: 14px;
128
+ border: 1px solid rgba(255, 255, 255, 0.3);
129
+ border-radius: 8px;
130
+ background: rgba(255, 255, 255, 0.9);
131
+ color: #333;
132
+ font-weight: bold;
133
+ cursor: pointer;
134
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
135
+ position: relative;
136
+ overflow: hidden;
137
+ backdrop-filter: blur(10px);
138
+ box-shadow:
139
+ 0 2px 8px rgba(0, 0, 0, 0.1),
140
+ inset 0 1px 0 rgba(255, 255, 255, 0.5);
141
+ }
142
+
143
+ .controls select::before,
144
+ .controls button::before {
145
+ content: '';
146
+ position: absolute;
147
+ top: 0;
148
+ right: 0;
149
+ width: 2px;
150
+ height: 100%;
151
+ background: linear-gradient(180deg, transparent, rgba(255, 215, 0, 0.6), transparent);
152
+ opacity: 0;
153
+ transition: opacity 0.3s ease;
154
+ }
155
+
156
+ .controls select:hover,
157
+ .controls button:hover {
158
+ background: rgba(255, 255, 255, 0.95);
159
+ transform: translateY(-1px);
160
+ box-shadow:
161
+ 0 4px 16px rgba(0, 0, 0, 0.15),
162
+ inset 0 1px 0 rgba(255, 255, 255, 0.7),
163
+ 0 0 20px rgba(255, 255, 255, 0.3);
164
+ }
165
+
166
+ .controls select:hover::before,
167
+ .controls button:hover::before {
168
+ opacity: 1;
169
+ }
170
+
171
+ .controls select:focus,
172
+ .controls button:focus {
173
+ outline: none;
174
+ box-shadow:
175
+ 0 0 0 3px rgba(255, 255, 255, 0.3),
176
+ 0 4px 16px rgba(0, 0, 0, 0.15),
177
+ inset 0 1px 0 rgba(255, 255, 255, 0.7);
178
+ }
179
+ /* 导航按钮样式 */
180
+ .nav-button {
181
+ padding: 10px 15px;
182
+ font-size: 14px;
183
+ border: 1px solid rgba(255, 255, 255, 0.3);
184
+ border-radius: 8px;
185
+ background: rgba(255, 255, 255, 0.9);
186
+ color: #333;
187
+ font-weight: bold;
188
+ cursor: pointer;
189
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
190
+ position: relative;
191
+ overflow: hidden;
192
+ backdrop-filter: blur(10px);
193
+ box-shadow:
194
+ 0 2px 8px rgba(0, 0, 0, 0.1),
195
+ inset 0 1px 0 rgba(255, 255, 255, 0.5);
196
+ }
197
+
198
+ .nav-button::before {
199
+ content: '';
200
+ position: absolute;
201
+ top: 0;
202
+ right: 0;
203
+ width: 2px;
204
+ height: 100%;
205
+ background: linear-gradient(180deg, transparent, rgba(255, 215, 0, 0.6), transparent);
206
+ opacity: 0;
207
+ transition: opacity 0.3s ease;
208
+ }
209
+
210
+ .nav-button:hover {
211
+ background: rgba(255, 255, 255, 0.95);
212
+ transform: translateY(-1px);
213
+ box-shadow:
214
+ 0 4px 16px rgba(0, 0, 0, 0.15),
215
+ inset 0 1px 0 rgba(255, 255, 255, 0.7),
216
+ 0 0 20px rgba(255, 255, 255, 0.3);
217
+ }
218
+
219
+ .nav-button:hover::before {
220
+ opacity: 1;
221
+ }
222
+
223
+ .nav-button:focus {
224
+ outline: none;
225
+ box-shadow:
226
+ 0 0 0 3px rgba(255, 255, 255, 0.3),
227
+ 0 4px 16px rgba(0, 0, 0, 0.15),
228
+ inset 0 1px 0 rgba(255, 255, 255, 0.7);
229
+ }
230
+ /* 时间表头样式 */
231
+ .time-header {
232
+ display: grid;
233
+ grid-template-columns: 70px repeat(7, 1fr);
234
+ background: rgba(255, 255, 255, 0.2);
235
+ backdrop-filter: blur(20px);
236
+ border: 1px solid rgba(255, 255, 255, 0.3);
237
+ color: white;
238
+ text-align: center;
239
+ margin: 10px 20px;
240
+ font-size: 14px;
241
+ font-weight: bold;
242
+ padding: 10px 0;
243
+ border-radius: 12px;
244
+ box-shadow:
245
+ 0 8px 32px rgba(0, 0, 0, 0.1),
246
+ inset 0 1px 0 rgba(255, 255, 255, 0.2),
247
+ 0 0 30px rgba(255, 255, 255, 0.1);
248
+ position: relative;
249
+ overflow: hidden;
250
+ }
251
+
252
+ .time-header::before {
253
+ content: '';
254
+ position: absolute;
255
+ top: 0;
256
+ left: 0;
257
+ right: 0;
258
+ bottom: 0;
259
+ background: linear-gradient(45deg,
260
+ rgba(255, 255, 255, 0.1) 0%,
261
+ transparent 50%,
262
+ rgba(255, 255, 255, 0.1) 100%);
263
+ animation: headerShimmer 4s ease-in-out infinite;
264
+ }
265
+
266
+ @keyframes headerShimmer {
267
+ 0%, 100% { opacity: 0.3; }
268
+ 50% { opacity: 0.7; }
269
+ }
270
+ .time-header div {
271
+ border-right: 1px solid rgba(255, 255, 255, 0.2);
272
+ padding: 5px;
273
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
274
+ position: relative;
275
+ z-index: 1;
276
+ }
277
+
278
+ .time-header div:last-child {
279
+ border-right: none;
280
+ }
281
+
282
+ .time-header div:hover {
283
+ transform: translateY(-3px) scale(1.02);
284
+ background: rgba(255, 255, 255, 0.25);
285
+ cursor: pointer;
286
+ box-shadow:
287
+ 0 4px 16px rgba(255, 255, 255, 0.2),
288
+ inset 0 1px 0 rgba(255, 255, 255, 0.3);
289
+ border-radius: 8px;
290
+ }
291
+ /* 时间列样式 */
292
+ .time-column {
293
+ background-color: #f8f9fa;
294
+ text-align: center;
295
+ font-size: 14px;
296
+ display: flex;
297
+ flex-direction: column;
298
+ border-right: 1px solid #ddd;
299
+ }
300
+ .time-column div {
301
+ flex: 1;
302
+ padding: 10px 0;
303
+ border-top: 1px solid #ddd;
304
+ font-weight: bold;
305
+ }
306
+ .time-column div:first-child {
307
+ border-top: none;
308
+ }
309
+ /* 每日课程样式 */
310
+ .day {
311
+ background: rgba(255, 255, 255, 0.15);
312
+ backdrop-filter: blur(15px);
313
+ border: 1px solid rgba(255, 255, 255, 0.2);
314
+ padding: 5px;
315
+ height: 500px;
316
+ display: flex;
317
+ flex-direction: column;
318
+ position: relative;
319
+ border-radius: 12px;
320
+ overflow: hidden;
321
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
322
+ box-shadow:
323
+ 0 4px 16px rgba(0, 0, 0, 0.1),
324
+ inset 0 1px 0 rgba(255, 255, 255, 0.2);
325
+ }
326
+
327
+ .day::before {
328
+ content: '';
329
+ position: absolute;
330
+ top: 0;
331
+ left: 0;
332
+ right: 0;
333
+ bottom: 0;
334
+ background: linear-gradient(135deg,
335
+ rgba(255, 255, 255, 0.1) 0%,
336
+ transparent 50%,
337
+ rgba(255, 255, 255, 0.05) 100%);
338
+ opacity: 0;
339
+ transition: opacity 0.3s ease;
340
+ pointer-events: none;
341
+ }
342
+
343
+ .day:hover {
344
+ transform: translateY(-8px) scale(1.02);
345
+ box-shadow:
346
+ 0 16px 40px rgba(0, 0, 0, 0.15),
347
+ inset 0 1px 0 rgba(255, 255, 255, 0.3),
348
+ 0 0 30px rgba(255, 255, 255, 0.2);
349
+ }
350
+
351
+ .day:hover::before {
352
+ opacity: 1;
353
+ }
354
+ /* 时间槽样式 */
355
+ .time-slot {
356
+ flex: 1;
357
+ position: relative;
358
+ border-top: 1px solid #eee;
359
+ overflow: hidden;
360
+ }
361
+ .time-slot:first-child {
362
+ border-top: none;
363
+ }
364
+ /* 课程样式 */
365
+ .time-slot .course {
366
+ position: absolute;
367
+ background: linear-gradient(135deg,
368
+ rgba(167, 216, 222, 0.9) 0%,
369
+ rgba(128, 199, 205, 0.9) 100%);
370
+ backdrop-filter: blur(10px);
371
+ border: 1px solid rgba(255, 255, 255, 0.3);
372
+ color: #333;
373
+ border-radius: 10px;
374
+ padding: 25px;
375
+ font-size: 15px;
376
+ font-weight: bold;
377
+ text-align: center;
378
+ margin: 5px;
379
+ width: 75%;
380
+ box-shadow:
381
+ 0 4px 16px rgba(0, 0, 0, 0.1),
382
+ inset 0 1px 0 rgba(255, 255, 255, 0.4),
383
+ 0 0 20px rgba(167, 216, 222, 0.3);
384
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
385
+ height: 50%;
386
+ position: relative;
387
+ overflow: hidden;
388
+ }
389
+
390
+ .time-slot .course::before {
391
+ content: '';
392
+ position: absolute;
393
+ top: 0;
394
+ left: -100%;
395
+ width: 100%;
396
+ height: 100%;
397
+ background: linear-gradient(90deg,
398
+ transparent,
399
+ rgba(255, 255, 255, 0.3),
400
+ transparent);
401
+ transition: left 0.6s ease;
402
+ }
403
+
404
+ .time-slot .course:hover {
405
+ transform: scale(1.08) translateY(-2px);
406
+ background: linear-gradient(135deg,
407
+ rgba(167, 216, 222, 0.95) 0%,
408
+ rgba(128, 199, 205, 0.95) 100%);
409
+ box-shadow:
410
+ 0 8px 32px rgba(0, 0, 0, 0.15),
411
+ inset 0 1px 0 rgba(255, 255, 255, 0.5),
412
+ 0 0 40px rgba(167, 216, 222, 0.5);
413
+ }
414
+
415
+ .time-slot .course:hover::before {
416
+ left: 100%;
417
+ }
418
+ /* 网格容器样式 */
419
+ .schedule-container {
420
+ display: grid;
421
+ grid-template-columns: 70px repeat(7, 1fr);
422
+ gap: 2px;
423
+ margin: 10px 20px;
424
+ background: rgba(255, 255, 255, 0.1);
425
+ backdrop-filter: blur(20px);
426
+ border: 1px solid rgba(255, 255, 255, 0.2);
427
+ border-radius: 15px;
428
+ overflow: hidden;
429
+ box-shadow:
430
+ 0 8px 32px rgba(0, 0, 0, 0.1),
431
+ inset 0 1px 0 rgba(255, 255, 255, 0.2),
432
+ 0 1px 0 rgba(255, 255, 255, 0.1);
433
+ position: relative;
434
+ }
435
+
436
+ .schedule-container::before {
437
+ content: '';
438
+ position: absolute;
439
+ top: 0;
440
+ left: 0;
441
+ right: 0;
442
+ bottom: 0;
443
+ background: linear-gradient(135deg,
444
+ rgba(255, 255, 255, 0.05) 0%,
445
+ transparent 50%,
446
+ rgba(255, 255, 255, 0.05) 100%);
447
+ pointer-events: none;
448
+ }
449
+
450
+ /* 课程详情模态框样式 */
451
+ .modal {
452
+ display: none;
453
+ position: fixed;
454
+ z-index: 1000;
455
+ left: 0;
456
+ top: 0;
457
+ width: 100%;
458
+ height: 100%;
459
+ background: rgba(0, 0, 0, 0.3);
460
+ backdrop-filter: blur(8px);
461
+ animation: modalFadeIn 0.3s ease-out;
462
+ }
463
+
464
+ @keyframes modalFadeIn {
465
+ from {
466
+ opacity: 0;
467
+ backdrop-filter: blur(0px);
468
+ }
469
+ to {
470
+ opacity: 1;
471
+ backdrop-filter: blur(8px);
472
+ }
473
+ }
474
+
475
+ .modal-content {
476
+ background: rgba(255, 255, 255, 0.95);
477
+ backdrop-filter: blur(20px);
478
+ margin: 10% auto;
479
+ padding: 0;
480
+ border: 1px solid rgba(255, 255, 255, 0.3);
481
+ border-radius: 15px;
482
+ width: 80%;
483
+ max-width: 500px;
484
+ box-shadow:
485
+ 0 16px 64px rgba(0, 0, 0, 0.2),
486
+ inset 0 1px 0 rgba(255, 255, 255, 0.4),
487
+ 0 0 40px rgba(255, 255, 255, 0.2);
488
+ animation: modalSlideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
489
+ position: relative;
490
+ overflow: hidden;
491
+ }
492
+
493
+ @keyframes modalSlideIn {
494
+ from {
495
+ transform: translateY(-50px) scale(0.9);
496
+ opacity: 0;
497
+ }
498
+ to {
499
+ transform: translateY(0) scale(1);
500
+ opacity: 1;
501
+ }
502
+ }
503
+
504
+ .modal-content::before {
505
+ content: '';
506
+ position: absolute;
507
+ top: 0;
508
+ left: 0;
509
+ right: 0;
510
+ bottom: 0;
511
+ background: linear-gradient(135deg,
512
+ rgba(255, 255, 255, 0.1) 0%,
513
+ transparent 50%,
514
+ rgba(255, 255, 255, 0.05) 100%);
515
+ pointer-events: none;
516
+ }
517
+
518
+ .modal-header {
519
+ background: rgba(255, 255, 255, 0.2);
520
+ backdrop-filter: blur(15px);
521
+ border: 1px solid rgba(255, 255, 255, 0.3);
522
+ color: white;
523
+ padding: 15px 20px;
524
+ border-radius: 15px 15px 0 0;
525
+ display: flex;
526
+ justify-content: space-between;
527
+ align-items: center;
528
+ position: relative;
529
+ z-index: 1;
530
+ box-shadow:
531
+ 0 2px 16px rgba(0, 0, 0, 0.1),
532
+ inset 0 1px 0 rgba(255, 255, 255, 0.3);
533
+ }
534
+
535
+ .modal-header::before {
536
+ content: '';
537
+ position: absolute;
538
+ top: 0;
539
+ left: 0;
540
+ right: 0;
541
+ bottom: 0;
542
+ background: linear-gradient(135deg,
543
+ rgba(255, 255, 255, 0.15) 0%,
544
+ rgba(255, 255, 255, 0.05) 100%);
545
+ border-radius: 15px 15px 0 0;
546
+ }
547
+
548
+ .modal-title {
549
+ font-size: 18px;
550
+ font-weight: bold;
551
+ position: relative;
552
+ z-index: 2;
553
+ text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
554
+ }
555
+
556
+ .close {
557
+ color: white;
558
+ font-size: 28px;
559
+ font-weight: bold;
560
+ cursor: pointer;
561
+ line-height: 1;
562
+ position: relative;
563
+ z-index: 2;
564
+ transition: all 0.3s ease;
565
+ text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
566
+ }
567
+
568
+ .close:hover {
569
+ opacity: 0.8;
570
+ transform: scale(1.1);
571
+ text-shadow: 0 0 20px rgba(255, 255, 255, 0.8);
572
+ }
573
+
574
+ .course-detail {
575
+ padding: 20px;
576
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
577
+ position: relative;
578
+ z-index: 1;
579
+ backdrop-filter: blur(5px);
580
+ transition: all 0.3s ease;
581
+ }
582
+
583
+ .course-detail:hover {
584
+ background: rgba(255, 255, 255, 0.1);
585
+ transform: translateX(5px);
586
+ }
587
+
588
+ .course-detail:last-child {
589
+ border-bottom: none;
590
+ }
591
+
592
+ .course-name {
593
+ font-size: 16px;
594
+ font-weight: bold;
595
+ color: #333;
596
+ margin-bottom: 10px;
597
+ text-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
598
+ }
599
+
600
+ .course-info {
601
+ margin: 5px 0;
602
+ color: #555;
603
+ text-shadow: 0 0 5px rgba(255, 255, 255, 0.2);
604
+ }
605
+
606
+ /* 移动端适配 */
607
+ @media (max-width: 768px) {
608
+ body {
609
+ font-size: 14px;
610
+ padding: 5px;
611
+ }
612
+ h1 {
613
+ font-size: 20px;
614
+ margin: 15px 0;
615
+ }
616
+ .controls {
617
+ display: flex;
618
+ flex-direction: row; /* 保持在一行内 */
619
+ flex-wrap: nowrap; /* 禁止换行 */
620
+ justify-content: space-around; /* 均匀分布各个控件 */
621
+ align-items: center; /* 纵向居中 */
622
+ padding: 10px 5px; /* 增加一些内边距,使其不显得太拥挤 */
623
+ margin: 5px;
624
+ gap: 5px; /* 控制组件之间的间距 */
625
+ }
626
+ .controls select,
627
+ .controls button {
628
+ padding: 6px; /* 适当减少内边距以适应小屏幕 */
629
+ font-size: 12px; /* 调整字体大小适应小屏幕 */
630
+ margin: 2px;
631
+ min-width: 80px; /* 设置最小宽度,确保控件在小屏幕上仍可点击 */
632
+ }
633
+
634
+ .time-header {
635
+ grid-template-columns: 25px repeat(7, 1fr);
636
+ font-size: 12px; /* 调整字体大小以适合移动端 */
637
+ padding: 12px 5px; /* 增加上下的内边距使其看起来更高 */
638
+ width: 95%;
639
+ margin: 10px 5px;
640
+ }
641
+ .schedule-container {
642
+ grid-template-columns: 22px repeat(7, minmax(0, 1fr));
643
+ margin: 3px;
644
+ display: grid;
645
+ gap: 1px;
646
+
647
+ background-color: #fff;
648
+ border-radius: 10px;
649
+ overflow: hidden;
650
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
651
+
652
+ }
653
+ .day {
654
+ padding: 3px;
655
+ border: 1px solid #ccc;
656
+ border-radius: 5px;
657
+ }
658
+ .time-column {
659
+ grid-column: 1;
660
+ text-align: center;
661
+ font-size: 15px;
662
+ display: flex;
663
+ flex-direction: column;
664
+ justify-content: center;
665
+ line-height: 1.2;
666
+
667
+ }
668
+ .time-slot .course {
669
+ font-size: 12px;
670
+ padding: 1px;
671
+ /*height: auto;*/
672
+ width: auto;
673
+ word-break: break-word; /* 确保长单词在小屏幕上也会换行 */
674
+ white-space: normal; /* 允许文本换行 */
675
+ overflow-wrap: break-word; /* 处理可能过长的文本,强制其换行 */
676
+ margin: 1px;
677
+ height: 90%;
678
+ }
679
+ }
680
+ /* .time-slot .course {*/
681
+ /* position: absolute;*/
682
+ /* background-color: #a7d8de;*/
683
+ /* color: #333;*/
684
+ /* border-radius: 5px;*/
685
+ /* padding: 25px;*/
686
+ /* font-size: 10px;*/
687
+ /* font-weight: bold;*/
688
+ /* text-align: center;*/
689
+ /* margin: 5px;*/
690
+ /* width: 65%;*/
691
+ /* !* height: 100%; *!*/
692
+ /* box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.1);*/
693
+ /* transition: transform 0.3s ease, background-color 0.3s ease;*/
694
+ /*}*/
695
+
696
+ </style>
697
+ </head>
698
+
699
+ <body>
700
+ <h1>信息技术课表</h1>
701
+ <div class="controls">
702
+ <div>
703
+ <label for="week-select">周次:</label>
704
+ <select id="week-select"></select>
705
+ </div>
706
+ <div>
707
+ <button class="nav-button" onclick="navigateToSection_01()">学生查询</button>
708
+ <button class="nav-button" onclick="navigateToSection_02()">教师查询</button>
709
+ <button class="nav-button" onclick="window.location.href='/classrooms'">教室查询</button>
710
+ </div>
711
+ <div>
712
+ <label for="grade-select">年级:</label>
713
+ <select id="grade-select">
714
+ <option value="22级">22级</option>
715
+ <option value="23级">23级</option>
716
+ <option value="24级">24级</option>
717
+ </select>
718
+ <label for="class-select">班级:</label>
719
+ <select id="class-select">
720
+ <!-- 动态生成 -->
721
+ </select>
722
+ </div>
723
+ </div>
724
+
725
+ <div class="time-header" id="time-header">
726
+ <div>时间</div>
727
+ <div><span>09/02</span><br><span>周一</span></div>
728
+ <div><span>09/03</span><br><span>周二</span></div>
729
+ <div><span>09/04</span><br><span>周三</span></div>
730
+ <div><span>09/05</span><br><span>周四</span></div>
731
+ <div><span>09/06</span><br><span>周五</span></div>
732
+ <div><span>09/07</span><br><span>周六</span></div>
733
+ <div><span>09/08</span><br><span>周日</span></div>
734
+ </div>
735
+
736
+ <div class="schedule-container" id="schedule">
737
+ <div class="time-column">
738
+ <div>1-3节</div>
739
+ <div>4-5节</div>
740
+ <div>6-8节</div>
741
+ <div>9-11节</div>
742
+ </div>
743
+ </div>
744
+
745
+ <!-- 课程详情模态框 -->
746
+ <div id="courseModal" class="modal">
747
+ <div class="modal-content">
748
+ <div class="modal-header">
749
+ <div class="modal-title">课程详情</div>
750
+ <span class="close" onclick="closeModal()">&times;</span>
751
+ </div>
752
+ <div id="courseDetails">
753
+ <!-- 课程详情将在这里动态生成 -->
754
+ </div>
755
+ </div>
756
+ </div>
757
+
758
+ <script>
759
+ function navigateToSection_01() {
760
+ window.location.href = "/students";
761
+ }
762
+ function navigateToSection_02() {
763
+ window.location.href = "/teachers";
764
+ }
765
+ let currentWeek = getCurrentWeek(); // 使用 getCurrentWeek() 函数获取当前周次
766
+
767
+ function initializeWeeks() {
768
+ const weekSelect = document.getElementById("week-select");
769
+ for (let i = 1; i <= 18; i++) {
770
+ const option = document.createElement("option");
771
+ option.value = i;
772
+ option.textContent = `第${i}周`;
773
+ if (i === currentWeek) {
774
+ option.selected = true; // 默认选中当前周次
775
+ }
776
+ weekSelect.appendChild(option);
777
+ }
778
+ }
779
+
780
+ function getCurrentWeek() {
781
+ const firstWeekStartDate = new Date("2024-09-02"); // 假设第一周从2024年9月2日(周一)开始
782
+ const today = new Date();
783
+ const timeDifference = today - firstWeekStartDate;
784
+ const daysDifference = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
785
+ return Math.floor(daysDifference / 7) + 1; // 根据天数计算当前周次
786
+ }
787
+ function loadClasses() {
788
+ const gradeSelect = document.getElementById("grade-select").value;
789
+ fetch(`/api/classes`)
790
+ .then(response => response.json())
791
+ .then(classes => {
792
+ const classSelect = document.getElementById("class-select");
793
+ classSelect.innerHTML = ""; // 清空班级选项
794
+ const defaultOption = document.createElement("option");
795
+ defaultOption.value = "";
796
+ defaultOption.textContent = "请选择班级";
797
+ defaultOption.selected = true;
798
+ classSelect.appendChild(defaultOption);
799
+ const filteredClasses = classes.filter(cls => cls.includes(gradeSelect.replace("级", "")));
800
+ filteredClasses.forEach(cls => {
801
+ const option = document.createElement("option");
802
+ option.value = cls;
803
+ option.textContent = cls;
804
+ classSelect.appendChild(option);
805
+ });
806
+ });
807
+ }
808
+ function randomColor() {
809
+ const colors = ["#ffcccb", "#c6e2ff", "#d5f5e3", "#f7dc6f", "#f1948a", "#aed6f1", "#d7bde2"];
810
+ return colors[Math.floor(Math.random() * colors.length)];
811
+ }
812
+
813
+ // 显示课程详情模态框
814
+ function showCourseDetails(courses) {
815
+ const modal = document.getElementById('courseModal');
816
+ const detailsContainer = document.getElementById('courseDetails');
817
+
818
+ // 清空之前的内容
819
+ detailsContainer.innerHTML = '';
820
+
821
+ // 为每个课程创建详情卡片
822
+ courses.forEach(course => {
823
+ const courseDetail = document.createElement('div');
824
+ courseDetail.className = 'course-detail';
825
+ courseDetail.innerHTML = `
826
+ <div class="course-name">${course.课程}</div>
827
+ <div class="course-info">教师: ${course.教师}</div>
828
+ <div class="course-info">班级: ${course.上课班级}</div>
829
+ <div class="course-info">时间: ${course.节次范围}</div>
830
+ <div class="course-info">地点: ${course.地点}</div>
831
+ <div class="course-info">校区: ${course.校区}</div>
832
+ <div class="course-info">周次: ${course.周次}</div>
833
+ `;
834
+ detailsContainer.appendChild(courseDetail);
835
+ });
836
+
837
+ // 显示模态框
838
+ modal.style.display = 'block';
839
+ }
840
+
841
+ // 关闭模态框
842
+ function closeModal() {
843
+ const modal = document.getElementById('courseModal');
844
+ modal.style.display = 'none';
845
+ }
846
+
847
+ // 点击模态框外部关闭
848
+ window.onclick = function(event) {
849
+ const modal = document.getElementById('courseModal');
850
+ if (event.target === modal) {
851
+ modal.style.display = 'none';
852
+ }
853
+ }
854
+
855
+ // 生成一周的日期
856
+ function generateWeekDates(startDate) {
857
+ const dates = [];
858
+ for (let i = 0; i < 7; i++) {
859
+ const date = new Date(startDate);
860
+ date.setDate(date.getDate() + i);
861
+ dates.push(date);
862
+ }
863
+ return dates;
864
+ }
865
+
866
+ // 更新时间表头
867
+ function updateTimeHeader(weekStartDate) {
868
+ const timeHeader = document.getElementById("time-header");
869
+ const weekDates = generateWeekDates(weekStartDate);
870
+ // 清空表头后重新生成
871
+ timeHeader.innerHTML = `
872
+ <div>时间</div>
873
+ `;
874
+ weekDates.forEach((date, index) => {
875
+ const dayOfWeek = "一二三四五六日"[index];
876
+ const formattedDate = date.toLocaleDateString("zh-CN", { month: "2-digit", day: "2-digit" });
877
+ const headerDiv = document.createElement("div");
878
+ headerDiv.innerHTML = `
879
+ <span>${formattedDate}</span>
880
+ <br />
881
+ <span>周${dayOfWeek}</span>
882
+ `;
883
+ timeHeader.appendChild(headerDiv);
884
+ });
885
+ }
886
+ function loadSchedule() {
887
+ const gradeSelect = document.getElementById("grade-select");
888
+ const classSelect = document.getElementById("class-select");
889
+ const weekSelect = document.getElementById("week-select");
890
+ const selectedGrade = gradeSelect.value;
891
+ const selectedClass = classSelect.value;
892
+ const selectedWeek = weekSelect.value;
893
+ if (!selectedClass) {
894
+ const scheduleContainer = document.getElementById("schedule");
895
+ scheduleContainer.innerHTML = "<p style='text-align: center; color: #777;'>请选择班级以查看课程表。</p>";
896
+ return;
897
+ }
898
+ let apiUrl = `/api/student_courses?week=${selectedWeek}`;
899
+ if (selectedGrade) apiUrl += `&grade=${encodeURIComponent(selectedGrade)}`;
900
+ if (selectedClass) apiUrl += `&admin_class=${encodeURIComponent(selectedClass)}`;
901
+ fetch(apiUrl)
902
+ .then(response => response.json())
903
+ .then(data => {
904
+ const scheduleContainer = document.getElementById("schedule");
905
+ scheduleContainer.innerHTML = `
906
+ <div class='time-column'>
907
+ <div>1-3节</div>
908
+ <div>4-5节</div>
909
+ <div>6-8节</div>
910
+ <div>9-11节</div>
911
+ </div>
912
+ `;
913
+ // 获取第一天日期(从后端返回或计算)
914
+ const firstDayDate = new Date(data[0]?.日期 || "2024-09-02"); // 默认值为 9 月 2 日
915
+ updateTimeHeader(firstDayDate);
916
+ // 初始化空白的课程表
917
+ const groupedByDay = Array.from({ length: 7 }, () => ({
918
+ intervals: Array.from({ length: 4 }, () => []),
919
+ }));
920
+ // 填充有课程的时间段
921
+ data.forEach(course => {
922
+ const dayIndex = course.星期 - 1;
923
+ const startPeriod = course.节次[0];
924
+ let intervalIndex = 0;
925
+ if (startPeriod >= 1 && startPeriod <= 3) {
926
+ intervalIndex = 0;
927
+ } else if (startPeriod >= 4 && startPeriod <= 5) {
928
+ intervalIndex = 1;
929
+ } else if (startPeriod >= 6 && startPeriod <= 8) {
930
+ intervalIndex = 2;
931
+ } else if (startPeriod >= 9 && startPeriod <= 11) {
932
+ intervalIndex = 3;
933
+ }
934
+ if (!groupedByDay[dayIndex].intervals[intervalIndex].length) {
935
+ groupedByDay[dayIndex].intervals[intervalIndex].push(course);
936
+ }
937
+ });
938
+ // 渲染课程表
939
+ groupedByDay.forEach((dayData, dayIndex) => {
940
+ const dayDiv = document.createElement("div");
941
+ dayDiv.className = "day";
942
+ dayData.intervals.forEach((intervalCourses, intervalIndex) => {
943
+ const intervalDiv = document.createElement("div");
944
+ intervalDiv.className = "time-slot";
945
+ if (intervalCourses.length > 0) {
946
+ const course = intervalCourses[0]; // 只展示一个课程
947
+ const courseDiv = document.createElement("div");
948
+ courseDiv.className = "course";
949
+ courseDiv.innerHTML = `${course.课程} ${course.校区} ${course.地点}`;
950
+ courseDiv.style.backgroundColor = randomColor(); // 随机背景颜色
951
+ courseDiv.onclick = () => showCourseDetails([course]);
952
+ courseDiv.style.cursor = "pointer";
953
+ intervalDiv.appendChild(courseDiv);
954
+ }
955
+ dayDiv.appendChild(intervalDiv);
956
+ });
957
+ scheduleContainer.appendChild(dayDiv);
958
+ });
959
+ });
960
+ }
961
+ document.getElementById("week-select").addEventListener("change", loadSchedule);
962
+ document.getElementById("grade-select").addEventListener("change", () => {
963
+ loadClasses();
964
+ loadSchedule();
965
+ });
966
+ document.getElementById("class-select").addEventListener("change", loadSchedule);
967
+ initializeWeeks();
968
+ loadClasses();
969
+
970
+ // 鼠标轨迹粒子效果
971
+ class MouseTrailParticle {
972
+ constructor(x, y) {
973
+ this.x = x;
974
+ this.y = y;
975
+ this.vx = (Math.random() - 0.5) * 4;
976
+ this.vy = (Math.random() - 0.5) * 4;
977
+ this.life = 1.0;
978
+ this.decay = 0.02 + Math.random() * 0.02;
979
+ this.size = 2 + Math.random() * 4;
980
+ this.color = `hsl(${Math.random() * 60 + 200}, 70%, 70%)`;
981
+ }
982
+
983
+ update() {
984
+ this.x += this.vx;
985
+ this.y += this.vy;
986
+ this.vx *= 0.98;
987
+ this.vy *= 0.98;
988
+ this.life -= this.decay;
989
+ this.size *= 0.98;
990
+ }
991
+
992
+ draw(ctx) {
993
+ ctx.save();
994
+ ctx.globalAlpha = this.life;
995
+ ctx.fillStyle = this.color;
996
+ ctx.shadowBlur = 10;
997
+ ctx.shadowColor = this.color;
998
+ ctx.beginPath();
999
+ ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
1000
+ ctx.fill();
1001
+ ctx.restore();
1002
+ }
1003
+
1004
+ isDead() {
1005
+ return this.life <= 0 || this.size <= 0.1;
1006
+ }
1007
+ }
1008
+
1009
+ // 创建画布
1010
+ const canvas = document.createElement('canvas');
1011
+ canvas.style.position = 'fixed';
1012
+ canvas.style.top = '0';
1013
+ canvas.style.left = '0';
1014
+ canvas.style.width = '100%';
1015
+ canvas.style.height = '100%';
1016
+ canvas.style.pointerEvents = 'none';
1017
+ canvas.style.zIndex = '9999';
1018
+ document.body.appendChild(canvas);
1019
+
1020
+ const ctx = canvas.getContext('2d');
1021
+ const particles = [];
1022
+ let mouseX = 0;
1023
+ let mouseY = 0;
1024
+ let lastMouseX = 0;
1025
+ let lastMouseY = 0;
1026
+
1027
+ function resizeCanvas() {
1028
+ canvas.width = window.innerWidth;
1029
+ canvas.height = window.innerHeight;
1030
+ }
1031
+
1032
+ function addParticles(x, y) {
1033
+ const distance = Math.sqrt((x - lastMouseX) ** 2 + (y - lastMouseY) ** 2);
1034
+ if (distance > 5) {
1035
+ for (let i = 0; i < 3; i++) {
1036
+ particles.push(new MouseTrailParticle(x + (Math.random() - 0.5) * 10, y + (Math.random() - 0.5) * 10));
1037
+ }
1038
+ lastMouseX = x;
1039
+ lastMouseY = y;
1040
+ }
1041
+ }
1042
+
1043
+ function animate() {
1044
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1045
+
1046
+ // 更新和绘制粒子
1047
+ for (let i = particles.length - 1; i >= 0; i--) {
1048
+ const particle = particles[i];
1049
+ particle.update();
1050
+ particle.draw(ctx);
1051
+
1052
+ if (particle.isDead()) {
1053
+ particles.splice(i, 1);
1054
+ }
1055
+ }
1056
+
1057
+ requestAnimationFrame(animate);
1058
+ }
1059
+
1060
+ // 事件监听
1061
+ document.addEventListener('mousemove', (e) => {
1062
+ mouseX = e.clientX;
1063
+ mouseY = e.clientY;
1064
+ addParticles(mouseX, mouseY);
1065
+ });
1066
+
1067
+ window.addEventListener('resize', resizeCanvas);
1068
+
1069
+ // 初始化
1070
+ resizeCanvas();
1071
+ animate();
1072
+ </script>
1073
+ </body>
1074
+ </html>
templates/student.html ADDED
@@ -0,0 +1,1100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>教师课表</title>
7
+ <style>
8
+ /* 全局样式 */
9
+ body {
10
+ font-family: 'Roboto', Arial, sans-serif;
11
+ margin: 0;
12
+ padding: 0;
13
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 25%, #f093fb 50%, #f5576c 75%, #4facfe 100%);
14
+ background-size: 400% 400%;
15
+ animation: gradientShift 15s ease infinite;
16
+ color: #333;
17
+ position: relative;
18
+ overflow-x: hidden;
19
+ }
20
+
21
+ /* 背景动画 */
22
+ @keyframes gradientShift {
23
+ 0% { background-position: 0% 50%; }
24
+ 50% { background-position: 100% 50%; }
25
+ 100% { background-position: 0% 50%; }
26
+ }
27
+
28
+ /* 磨砂玻璃背景层 */
29
+ body::before {
30
+ content: '';
31
+ position: fixed;
32
+ top: 0;
33
+ left: 0;
34
+ width: 100%;
35
+ height: 100%;
36
+ background: rgba(255, 255, 255, 0.1);
37
+ backdrop-filter: blur(10px);
38
+ z-index: -1;
39
+ pointer-events: none;
40
+ }
41
+
42
+ /* 浮动粒子效果 */
43
+ body::after {
44
+ content: '';
45
+ position: fixed;
46
+ top: 0;
47
+ left: 0;
48
+ width: 100%;
49
+ height: 100%;
50
+ background-image:
51
+ radial-gradient(circle at 20% 80%, rgba(255, 255, 255, 0.1) 2px, transparent 2px),
52
+ radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.15) 1px, transparent 1px),
53
+ radial-gradient(circle at 40% 40%, rgba(255, 255, 255, 0.08) 3px, transparent 3px);
54
+ background-size: 200px 200px, 150px 150px, 300px 300px;
55
+ animation: particleFloat 20s linear infinite;
56
+ z-index: -1;
57
+ pointer-events: none;
58
+ }
59
+
60
+ @keyframes particleFloat {
61
+ 0% { transform: translateY(0px) rotate(0deg); }
62
+ 100% { transform: translateY(-100vh) rotate(360deg); }
63
+ }
64
+
65
+ h1 {
66
+ text-align: center;
67
+ margin: 20px 0;
68
+ font-size: 28px;
69
+ font-weight: bold;
70
+ color: #0078d4;
71
+ letter-spacing: 1.2px;
72
+ }
73
+
74
+ /* 控制区域样式 */
75
+ .controls {
76
+ display: flex;
77
+ justify-content: space-between;
78
+ align-items: center;
79
+ padding: 15px 20px;
80
+ background: rgba(255, 255, 255, 0.3);
81
+ backdrop-filter: blur(20px);
82
+ color: #0078d4;
83
+ border-radius: 15px;
84
+ border: 1px solid rgba(255, 255, 255, 0.2);
85
+ margin: 10px 20px;
86
+ box-shadow:
87
+ 0 8px 32px rgba(0, 0, 0, 0.1),
88
+ 0 2px 8px rgba(0, 0, 0, 0.05),
89
+ inset 0 1px 0 rgba(255, 255, 255, 0.3);
90
+ position: relative;
91
+ z-index: 10;
92
+ }
93
+
94
+ .controls::before {
95
+ content: '';
96
+ position: absolute;
97
+ top: 0;
98
+ left: 0;
99
+ right: 0;
100
+ bottom: 0;
101
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
102
+ border-radius: 15px;
103
+ z-index: -1;
104
+ animation: controlsShimmer 3s ease-in-out infinite;
105
+ }
106
+
107
+ @keyframes controlsShimmer {
108
+ 0%, 100% { opacity: 0.5; }
109
+ 50% { opacity: 0.8; }
110
+ }
111
+
112
+ .controls > div {
113
+ flex: 1; /* 让每个子元素均匀分布 */
114
+ text-align: center; /* 居中对齐子元素 */
115
+ }
116
+
117
+ .nav-button {
118
+ padding: 10px 15px;
119
+ font-size: 14px;
120
+ border: none;
121
+ border-radius: 5px;
122
+ background-color: #fff;
123
+ color: #0078d4;
124
+ font-weight: bold;
125
+ cursor: pointer;
126
+ transition: all 0.3s ease;
127
+ }
128
+ .nav-button:hover {
129
+ background-color: #eaf4fc;
130
+ }
131
+ .nav-button:focus {
132
+ outline: none;
133
+ box-shadow: 0 0 5px rgba(0, 120, 212, 0.8);
134
+ }
135
+
136
+
137
+ .controls label {
138
+ font-weight: bold;
139
+ margin-right: 10px;
140
+ }
141
+
142
+ .controls select, .controls button {
143
+ padding: 12px 24px;
144
+ font-size: 14px;
145
+ border: 1px solid rgba(255, 255, 255, 0.3);
146
+ border-radius: 8px;
147
+ background: rgba(255, 255, 255, 0.9);
148
+ color: #0078d4;
149
+ font-weight: bold;
150
+ cursor: pointer;
151
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
152
+ backdrop-filter: blur(10px);
153
+ position: relative;
154
+ overflow: hidden;
155
+ box-shadow:
156
+ 0 2px 10px rgba(0, 0, 0, 0.1),
157
+ inset 0 1px 0 rgba(255, 255, 255, 0.5);
158
+ }
159
+
160
+ .controls button {
161
+ background: linear-gradient(135deg, rgba(0, 120, 212, 0.8) 0%, rgba(0, 90, 158, 0.9) 100%);
162
+ color: white;
163
+ box-shadow:
164
+ 0 4px 15px rgba(0, 120, 212, 0.3),
165
+ inset 0 1px 0 rgba(255, 255, 255, 0.2);
166
+ }
167
+
168
+ .controls button::before {
169
+ content: '';
170
+ position: absolute;
171
+ top: 0;
172
+ left: -100%;
173
+ width: 100%;
174
+ height: 100%;
175
+ background: linear-gradient(90deg, transparent, rgba(255, 215, 0, 0.4), transparent);
176
+ transition: left 0.5s;
177
+ }
178
+
179
+ .controls select:hover, .controls button:hover {
180
+ transform: translateY(-2px);
181
+ box-shadow:
182
+ 0 8px 25px rgba(0, 120, 212, 0.4),
183
+ inset 0 1px 0 rgba(255, 255, 255, 0.3);
184
+ }
185
+
186
+ .controls button:hover {
187
+ background: linear-gradient(135deg, rgba(0, 90, 158, 0.9) 0%, rgba(0, 120, 212, 1) 100%);
188
+ }
189
+
190
+ .controls button:hover::before {
191
+ left: 100%;
192
+ }
193
+
194
+ .controls select:hover {
195
+ background: rgba(255, 255, 255, 1);
196
+ transform: translateY(-1px);
197
+ box-shadow:
198
+ 0 4px 15px rgba(0, 0, 0, 0.15),
199
+ inset 0 1px 0 rgba(255, 255, 255, 0.7);
200
+ }
201
+
202
+ .controls select:focus, .controls button:focus {
203
+ outline: none;
204
+ box-shadow: 0 0 5px rgba(0, 120, 212, 0.8);
205
+ }
206
+
207
+ /* 时间表头样式 */
208
+ .time-header {
209
+ display: grid;
210
+ grid-template-columns: 70px repeat(7, 1fr);
211
+ background: rgba(255, 255, 255, 0.3);
212
+ backdrop-filter: blur(20px);
213
+ color: #0078d4;
214
+ text-align: center;
215
+ margin: 10px 20px;
216
+ font-size: 14px;
217
+ font-weight: bold;
218
+ padding: 10px 0;
219
+ border-radius: 12px;
220
+ border: 1px solid rgba(255, 255, 255, 0.2);
221
+ box-shadow:
222
+ 0 8px 32px rgba(0, 0, 0, 0.1),
223
+ 0 2px 8px rgba(0, 0, 0, 0.05),
224
+ inset 0 1px 0 rgba(255, 255, 255, 0.3),
225
+ inset 0 -1px 0 rgba(0, 0, 0, 0.1);
226
+ position: relative;
227
+ overflow: hidden;
228
+ }
229
+
230
+ .time-header::before {
231
+ content: '';
232
+ position: absolute;
233
+ top: 0;
234
+ left: -100%;
235
+ width: 100%;
236
+ height: 100%;
237
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
238
+ animation: headerShimmer 4s ease-in-out infinite;
239
+ }
240
+
241
+ @keyframes headerShimmer {
242
+ 0% { left: -100%; }
243
+ 50% { left: 100%; }
244
+ 100% { left: 100%; }
245
+ }
246
+
247
+ .time-header div {
248
+ border-right: 1px solid rgba(255, 255, 255, 0.2);
249
+ padding: 8px;
250
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
251
+ position: relative;
252
+ z-index: 1;
253
+ }
254
+
255
+ .time-header div:last-child {
256
+ border-right: none;
257
+ }
258
+
259
+ /* 鼠标悬停时样式 */
260
+ .time-header div:hover {
261
+ transform: translateY(-3px) scale(1.02);
262
+ background: rgba(255, 255, 255, 0.4);
263
+ backdrop-filter: blur(15px);
264
+ cursor: pointer;
265
+ border-radius: 8px;
266
+ box-shadow:
267
+ 0 8px 25px rgba(0, 120, 212, 0.2),
268
+ 0 4px 12px rgba(0, 0, 0, 0.1),
269
+ inset 0 1px 0 rgba(255, 255, 255, 0.5);
270
+ }
271
+
272
+
273
+ /* 时间列样式 */
274
+ .time-column {
275
+ background-color: #f8f9fa;
276
+ text-align: center;
277
+ font-size: 14px;
278
+ display: flex;
279
+ flex-direction: column;
280
+ border-right: 1px solid #ddd;
281
+ }
282
+
283
+ .time-column div {
284
+ flex: 1;
285
+ padding: 10px 0;
286
+ border-top: 1px solid #ddd;
287
+ font-weight: bold;
288
+ }
289
+
290
+ .time-column div:first-child {
291
+ border-top: none;
292
+ }
293
+
294
+ /* 每日课程样式 */
295
+ .day {
296
+ background: rgba(255, 255, 255, 0.3);
297
+ backdrop-filter: blur(15px);
298
+ border: 1px solid rgba(255, 255, 255, 0.2);
299
+ padding: 8px;
300
+ height: 500px;
301
+ display: flex;
302
+ flex-direction: column;
303
+ position: relative;
304
+ border-radius: 12px;
305
+ overflow: hidden;
306
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
307
+ box-shadow:
308
+ 0 4px 20px rgba(0, 0, 0, 0.1),
309
+ 0 1px 4px rgba(0, 0, 0, 0.05),
310
+ inset 0 1px 0 rgba(255, 255, 255, 0.3);
311
+ }
312
+
313
+ .day::before {
314
+ content: '';
315
+ position: absolute;
316
+ top: 0;
317
+ left: 0;
318
+ right: 0;
319
+ bottom: 0;
320
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
321
+ opacity: 0;
322
+ transition: opacity 0.3s ease;
323
+ pointer-events: none;
324
+ }
325
+
326
+ .day:hover {
327
+ transform: translateY(-8px) scale(1.02);
328
+ box-shadow:
329
+ 0 12px 40px rgba(0, 0, 0, 0.15),
330
+ 0 4px 12px rgba(0, 0, 0, 0.1),
331
+ inset 0 1px 0 rgba(255, 255, 255, 0.4);
332
+ }
333
+
334
+ .day:hover::before {
335
+ opacity: 1;
336
+ }
337
+
338
+ /* 时间槽样式 */
339
+ .time-slot {
340
+ flex: 1;
341
+ position: relative;
342
+ border-top: 1px solid #eee;
343
+ overflow: hidden;
344
+ }
345
+
346
+ .time-slot:first-child {
347
+ border-top: none;
348
+ }
349
+
350
+ /* 课程样式 */
351
+ .time-slot .course {
352
+ position: absolute;
353
+ background: linear-gradient(135deg, rgba(167, 216, 222, 0.9) 0%, rgba(135, 206, 235, 0.8) 100%);
354
+ backdrop-filter: blur(10px);
355
+ color: #2c3e50;
356
+ border: 1px solid rgba(255, 255, 255, 0.3);
357
+ border-radius: 10px;
358
+ padding: 25px;
359
+ font-size: 15px;
360
+ font-weight: bold;
361
+ text-align: center;
362
+ margin: 5px;
363
+ width: 75%;
364
+ box-shadow:
365
+ 0 8px 25px rgba(0, 0, 0, 0.15),
366
+ 0 3px 10px rgba(0, 0, 0, 0.1),
367
+ inset 0 1px 0 rgba(255, 255, 255, 0.4),
368
+ inset 0 -1px 0 rgba(0, 0, 0, 0.1);
369
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
370
+ height: 50%;
371
+ position: relative;
372
+ overflow: hidden;
373
+ }
374
+
375
+ .time-slot .course::before {
376
+ content: '';
377
+ position: absolute;
378
+ top: 0;
379
+ left: -100%;
380
+ width: 100%;
381
+ height: 100%;
382
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
383
+ transition: left 0.6s ease;
384
+ }
385
+
386
+ .time-slot .course:hover {
387
+ transform: translateY(-5px) scale(1.05);
388
+ background: linear-gradient(135deg, rgba(167, 216, 222, 1) 0%, rgba(135, 206, 235, 0.95) 100%);
389
+ box-shadow:
390
+ 0 15px 35px rgba(0, 0, 0, 0.2),
391
+ 0 5px 15px rgba(0, 0, 0, 0.15),
392
+ inset 0 1px 0 rgba(255, 255, 255, 0.5),
393
+ inset 0 -1px 0 rgba(0, 0, 0, 0.1);
394
+ }
395
+
396
+ .time-slot .course:hover::before {
397
+ left: 100%;
398
+ }
399
+
400
+
401
+ .search-container {
402
+
403
+
404
+ position: relative; /* 使搜索结果相对搜索框定位 */
405
+ }
406
+
407
+ /* 课程详情模态框样式 */
408
+ .modal {
409
+ display: none;
410
+ position: fixed;
411
+ z-index: 1000;
412
+ left: 0;
413
+ top: 0;
414
+ width: 100%;
415
+ height: 100%;
416
+ background: rgba(0, 0, 0, 0.6);
417
+ backdrop-filter: blur(8px);
418
+ animation: modalFadeIn 0.3s ease-out;
419
+ }
420
+
421
+ @keyframes modalFadeIn {
422
+ from {
423
+ opacity: 0;
424
+ backdrop-filter: blur(0px);
425
+ }
426
+ to {
427
+ opacity: 1;
428
+ backdrop-filter: blur(8px);
429
+ }
430
+ }
431
+
432
+ .modal-content {
433
+ background: rgba(255, 255, 255, 0.95);
434
+ backdrop-filter: blur(20px);
435
+ margin: 10% auto;
436
+ padding: 0;
437
+ border: 1px solid rgba(255, 255, 255, 0.3);
438
+ border-radius: 15px;
439
+ width: 80%;
440
+ max-width: 500px;
441
+ box-shadow:
442
+ 0 20px 60px rgba(0, 0, 0, 0.3),
443
+ 0 8px 25px rgba(0, 0, 0, 0.2),
444
+ inset 0 1px 0 rgba(255, 255, 255, 0.4);
445
+ animation: modalSlideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);
446
+ position: relative;
447
+ overflow: hidden;
448
+ }
449
+
450
+ .modal-content::before {
451
+ content: '';
452
+ position: absolute;
453
+ top: 0;
454
+ left: 0;
455
+ right: 0;
456
+ bottom: 0;
457
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
458
+ pointer-events: none;
459
+ }
460
+
461
+ @keyframes modalSlideIn {
462
+ from {
463
+ transform: translateY(-50px) scale(0.9);
464
+ opacity: 0;
465
+ }
466
+ to {
467
+ transform: translateY(0) scale(1);
468
+ opacity: 1;
469
+ }
470
+ }
471
+
472
+ .modal-header {
473
+ background: rgba(255, 255, 255, 0.3);
474
+ backdrop-filter: blur(15px);
475
+ color: #0078d4;
476
+ padding: 20px;
477
+ border-radius: 15px 15px 0 0;
478
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
479
+ display: flex;
480
+ justify-content: space-between;
481
+ align-items: center;
482
+ position: relative;
483
+ box-shadow:
484
+ 0 4px 15px rgba(0, 0, 0, 0.1),
485
+ inset 0 1px 0 rgba(255, 255, 255, 0.3);
486
+ }
487
+
488
+ .modal-header::before {
489
+ content: '';
490
+ position: absolute;
491
+ top: 0;
492
+ left: 0;
493
+ right: 0;
494
+ bottom: 0;
495
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
496
+ pointer-events: none;
497
+ }
498
+
499
+ .modal-title {
500
+ font-size: 20px;
501
+ font-weight: bold;
502
+ position: relative;
503
+ z-index: 1;
504
+ text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5);
505
+ }
506
+
507
+ .close {
508
+ color: #0078d4;
509
+ font-size: 28px;
510
+ font-weight: bold;
511
+ cursor: pointer;
512
+ line-height: 1;
513
+ position: relative;
514
+ z-index: 1;
515
+ text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5);
516
+ transition: all 0.3s ease;
517
+ }
518
+
519
+ .close:hover {
520
+ transform: scale(1.1);
521
+ color: #005a9e;
522
+ text-shadow: 0 2px 4px rgba(0, 120, 212, 0.3);
523
+ }
524
+
525
+ .course-detail {
526
+ padding: 20px;
527
+ border-bottom: 1px solid rgba(255, 255, 255, 0.3);
528
+ backdrop-filter: blur(5px);
529
+ transition: all 0.3s ease;
530
+ position: relative;
531
+ }
532
+
533
+ .course-detail:hover {
534
+ background: rgba(255, 255, 255, 0.1);
535
+ transform: translateX(5px);
536
+ }
537
+
538
+ .course-detail:last-child {
539
+ border-bottom: none;
540
+ }
541
+
542
+ .course-name {
543
+ font-size: 18px;
544
+ font-weight: bold;
545
+ color: #0078d4;
546
+ margin-bottom: 10px;
547
+ text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5);
548
+ }
549
+
550
+ .course-info {
551
+ margin: 8px 0;
552
+ color: #555;
553
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.3);
554
+ }
555
+
556
+ .teacher-search {
557
+ width: 150px; /* 搜索框固定宽度 */
558
+ padding: 8px 12px;
559
+ font-size: 14px;
560
+ border: 1px solid #0078d4; /* 蓝色边框 */
561
+ border-radius: 5px;
562
+ color: #333;
563
+ box-sizing: border-box; /* 确保宽度包含内边距和边框 */
564
+ }
565
+
566
+ .search-results {
567
+ position: absolute; /* 使结果相对于搜索框容器定位 */
568
+ top: calc(100% + 4px); /* 紧贴搜索框下方,并增加 4px 间距 */
569
+ left: 42.5%; /* 确保左对齐 */
570
+ width: 150px; /* 与搜索框宽度一致 */
571
+ background: #f0f8ff; /* 浅蓝背景 */
572
+ border: 1px solid #0078d4; /* 与搜索框相同的蓝色边框 */
573
+ max-height: 150px; /* 最大高度 */
574
+ overflow-y: auto; /* 启用滚动条 */
575
+ z-index: 1000; /* 确保结果在顶层显示 */
576
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* 增加阴影 */
577
+ border-radius: 5px; /* 与搜索框一致的圆角 */
578
+ }
579
+
580
+ .search-results div {
581
+ padding: 8px 12px; /* 每个选项的内边距 */
582
+ font-size: 14px; /* 字体大小 */
583
+ color: #333; /* 默认文字颜色 */
584
+ cursor: pointer; /* 鼠标悬停时显示为手型 */
585
+ transition: background 0.3s, color 0.3s; /* 添加平滑过渡效果 */
586
+ }
587
+
588
+ .search-results div:hover {
589
+ background-color: #0078d4; /* 鼠标悬停时背景色 */
590
+ color: #fff; /* 悬停时文字颜色 */
591
+ }
592
+
593
+
594
+
595
+
596
+
597
+ /* 网格容器样式 */
598
+ .schedule-container {
599
+ display: grid;
600
+ grid-template-columns: 70px repeat(7, 1fr);
601
+ gap: 2px;
602
+ margin: 10px 20px;
603
+ background-color: #fff;
604
+ border-radius: 10px;
605
+ overflow: hidden;
606
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
607
+ }
608
+ @media (max-width: 768px) {
609
+ body {
610
+ font-size: 14px;
611
+ padding: 5px;
612
+ }
613
+ h1 {
614
+ font-size: 20px;
615
+ margin: 15px 0;
616
+ }
617
+ .controls {
618
+ display: flex;
619
+ flex-direction: row; /* 保持在一行内 */
620
+ flex-wrap: nowrap; /* 禁止换行 */
621
+ justify-content: space-around; /* 均匀分布各个控件 */
622
+ align-items: center; /* 纵向居中 */
623
+ padding: 10px 5px; /* 增加一些内边距,使其不显得太拥挤 */
624
+ margin: 5px;
625
+ gap: 5px; /* 控制组件之间的间距 */
626
+ }
627
+ .controls select,
628
+ .controls button {
629
+ padding: 6px; /* 适当减少内边距以适应小屏幕 */
630
+ font-size: 12px; /* 调整字体大小适应小屏幕 */
631
+ margin: 2px;
632
+ min-width: 80px; /* 设置最小宽度,确保控件在小屏幕上仍可点击 */
633
+ }
634
+
635
+ .time-header {
636
+ grid-template-columns: 25px repeat(7, 1fr);
637
+ font-size: 12px; /* 调整字体大小以适合移动端 */
638
+ padding: 12px 5px; /* 增加上下的内边距使其看起来更高 */
639
+ width: 95%;
640
+ margin: 10px 5px;
641
+ }
642
+ .schedule-container {
643
+ grid-template-columns: 22px repeat(7, minmax(0, 1fr));
644
+ margin: 3px;
645
+ display: grid;
646
+ gap: 1px;
647
+
648
+ background-color: #fff;
649
+ border-radius: 10px;
650
+ overflow: hidden;
651
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
652
+
653
+ }
654
+ .day {
655
+ padding: 3px;
656
+ border: 1px solid #ccc;
657
+ border-radius: 5px;
658
+ }
659
+ .time-column {
660
+ grid-column: 1;
661
+ text-align: center;
662
+ font-size: 15px;
663
+ display: flex;
664
+ flex-direction: column;
665
+ justify-content: center;
666
+ line-height: 1.2;
667
+
668
+ }
669
+ .time-slot .course {
670
+ font-size: 12px;
671
+ padding: 1px;
672
+ /*height: auto;*/
673
+ width: auto;
674
+ word-break: break-word; /* 确保长单词在小屏幕上也会换行 */
675
+ white-space: normal; /* 允许文本换行 */
676
+ overflow-wrap: break-word; /* 处理可能过长的文本,强制其换行 */
677
+ margin: 1px;
678
+ height: 90%;
679
+ }
680
+ .teacher-search {
681
+ width: 100%; /* 搜索框固定宽度 */
682
+ padding: 8px 12px;
683
+ font-size: 14px;
684
+ border: 1px solid #0078d4; /* 蓝色边框 */
685
+ border-radius: 5px;
686
+ color: #333;
687
+ box-sizing: border-box; /* 确保宽度包含内边距和边框 */
688
+ }
689
+ .search-results {
690
+ position: absolute;
691
+ top: calc(100% + 2px); /* 列表紧贴搜索框下方,留 2px 的间隙 */
692
+ left: 0%; /* 与搜索框左侧完全对齐 */
693
+ width: 100%; /* 列表宽度与搜索框保持一致 */
694
+ background: #f0f8ff; /* 浅蓝背景 */
695
+ border: 1px solid #0078d4; /* 边框颜色 */
696
+ max-height: 150px; /* 最大高度,超出部分滚动 */
697
+ overflow-y: auto; /* 启用垂直滚动条 */
698
+ z-index: 1000; /* 确保显示在最上层 */
699
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
700
+ border-radius: 5px; /* 圆角边框 */
701
+ box-sizing: border-box; /* 确保宽度计算准确 */
702
+ }
703
+ }
704
+ </style>
705
+ </head>
706
+ <body>
707
+ <h1>学生课表</h1>
708
+ <div class="controls">
709
+
710
+
711
+ <div class="search-container">
712
+ <label for="teacher-search">学生:</label>
713
+ <input type="text" id="teacher-search" class="teacher-search" placeholder="搜索学生名字">
714
+ <div id="search-results" class="search-results"></div>
715
+ </div>
716
+
717
+ <div>
718
+ <button class="nav-button" onclick="window.location.href='/'">班级查询</button>
719
+ <button class="nav-button" onclick="window.location.href='/teachers'">教师查询</button>
720
+ <button class="nav-button" onclick="window.location.href='/classrooms'">教室查询</button>
721
+ </div>
722
+ <div>
723
+ <label for="week-select">周次:</label>
724
+ <select id="week-select"></select>
725
+ </div>
726
+ </div>
727
+
728
+ <div class="time-header" id="time-header">
729
+ <div>时间</div>
730
+ <div>周一</div>
731
+ <div>周二</div>
732
+ <div>周三</div>
733
+ <div>周四</div>
734
+ <div>周五</div>
735
+ <div>周六</div>
736
+ <div>周日</div>
737
+ </div>
738
+
739
+ <div class="schedule-container" id="schedule">
740
+ <div class="time-column">
741
+ <div>1-3节</div>
742
+ <div>4-5节</div>
743
+ <div>6-8节</div>
744
+ <div>9-11节</div>
745
+ </div>
746
+ </div>
747
+
748
+ <!-- 课程详情模态框 -->
749
+ <div id="courseModal" class="modal">
750
+ <div class="modal-content">
751
+ <div class="modal-header">
752
+ <div class="modal-title">课程详情</div>
753
+ <span class="close" onclick="closeModal()">&times;</span>
754
+ </div>
755
+ <div id="courseDetails">
756
+ <!-- 课程详情将在这里动态生成 -->
757
+ </div>
758
+ </div>
759
+ </div>
760
+
761
+ <script>
762
+ let currentWeek = getCurrentWeek(); // 使用 getCurrentWeek() 函数获取当前周次
763
+ let teacherList = [];
764
+ function initializeWeeks() {
765
+ const weekSelect = document.getElementById("week-select");
766
+ for (let i = 1; i <= 18; i++) {
767
+ const option = document.createElement("option");
768
+ option.value = i;
769
+ option.textContent = `第${i}周`;
770
+ if (i === currentWeek) {
771
+ option.selected = true; // 默认选中当前周次
772
+ }
773
+ weekSelect.appendChild(option);
774
+ }
775
+ }
776
+ function getCurrentWeek() {
777
+ const firstWeekStartDate = new Date("2024-09-02"); // 假设第一周从2024年9月2日(周一)开始
778
+ const today = new Date();
779
+ const timeDifference = today - firstWeekStartDate;
780
+ const daysDifference = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
781
+ return Math.floor(daysDifference / 7) + 1; // 根据天数计算当前周次
782
+ }
783
+ function loadTeachers() {
784
+ fetch("/api/students")
785
+ .then(response => response.json())
786
+ .then(teachers => {
787
+ teacherList = teachers.sort((a, b) => {
788
+ if (a === "秦振凯") return -1;
789
+ if (b === "秦振凯") return 1;
790
+ return a.localeCompare(b);
791
+ });
792
+ })
793
+ .catch(error => console.error("加载学生列表出错:", error));
794
+ }
795
+
796
+ function searchTeachers() {
797
+ const searchInput = document.getElementById("teacher-search").value.toLowerCase();
798
+ const searchResults = document.getElementById("search-results");
799
+ searchResults.innerHTML = "";
800
+
801
+ if (searchInput.trim() === "") {
802
+ return;
803
+ }
804
+
805
+ const filteredTeachers = teacherList.filter(teacher =>
806
+ teacher.toLowerCase().includes(searchInput)
807
+ );
808
+
809
+ filteredTeachers.forEach(teacher => {
810
+ const resultDiv = document.createElement("div");
811
+ resultDiv.textContent = teacher;
812
+ resultDiv.onclick = () => selectTeacher(teacher);
813
+ searchResults.appendChild(resultDiv);
814
+ });
815
+ }
816
+
817
+ function selectTeacher(teacher) {
818
+ document.getElementById("teacher-search").value = teacher;
819
+ document.getElementById("search-results").innerHTML = "";
820
+ loadSchedule();
821
+ }
822
+
823
+ function loadSchedule() {
824
+ const teacherSearch = document.getElementById("teacher-search").value;
825
+ const weekSelect = document.getElementById("week-select");
826
+ const selectedWeek = parseInt(weekSelect.value);
827
+ const apiUrl = `/api/student_courses_v2?week=${selectedWeek}&student_name=${encodeURIComponent(teacherSearch)}`;
828
+
829
+ // 先清空之前的表头和课程表内容,避免混淆
830
+ document.getElementById("time-header").innerHTML = '';
831
+ document.getElementById("schedule").innerHTML = `
832
+ <div class='time-column'>
833
+ <div>1-3节</div>
834
+ <div>4-5节</div>
835
+ <div>6-8节</div>
836
+ <div>9-11节</div>
837
+ </div>
838
+ `;
839
+
840
+ // 获取所选周次的起始日期
841
+ const firstWeekStartDate = new Date("2024-09-02"); // 假设第一周从2024年9月2日(周一)开始
842
+ const selectedWeekStartDate = new Date(firstWeekStartDate);
843
+ selectedWeekStartDate.setDate(firstWeekStartDate.getDate() + (selectedWeek - 1) * 7);
844
+
845
+ // 更新时间表头
846
+ updateTimeHeader(selectedWeekStartDate);
847
+
848
+ fetch(apiUrl)
849
+ .then(response => response.json())
850
+ .then(data => {
851
+ if (data.length === 0) {
852
+ console.warn("未获取到课程数据");
853
+ return;
854
+ }
855
+
856
+ // 初始化空白课程表
857
+ const groupedByDay = Array.from({ length: 7 }, () => ({
858
+ intervals: Array.from({ length: 4 }, () => [])
859
+ }));
860
+
861
+ // 填充课程
862
+ data.forEach(course => {
863
+ const dayIndex = course.星期 - 1; // 星期一为0,星期日为6
864
+ const startPeriod = course.节次[0];
865
+
866
+ let intervalIndex = 0;
867
+ if (startPeriod >= 1 && startPeriod <= 3) intervalIndex = 0;
868
+ else if (startPeriod >= 4 && startPeriod <= 5) intervalIndex = 1;
869
+ else if (startPeriod >= 6 && startPeriod <= 8) intervalIndex = 2;
870
+ else if (startPeriod >= 9 && startPeriod <= 11) intervalIndex = 3;
871
+
872
+ groupedByDay[dayIndex].intervals[intervalIndex].push(course);
873
+ });
874
+
875
+ // 渲染课程表
876
+ const scheduleContainer = document.getElementById("schedule");
877
+ groupedByDay.forEach((dayData, dayIndex) => {
878
+ const dayDiv = document.createElement("div");
879
+ dayDiv.className = "day";
880
+
881
+ dayData.intervals.forEach((intervalCourses, intervalIndex) => {
882
+ const intervalDiv = document.createElement("div");
883
+ intervalDiv.className = "time-slot";
884
+
885
+ if (intervalCourses.length > 0) {
886
+ const course = intervalCourses[0];
887
+ const courseDiv = document.createElement("div");
888
+ courseDiv.className = "course";
889
+ courseDiv.innerHTML = `${course.课程} ${course.校区} ${course.地点}`;
890
+ courseDiv.style.backgroundColor = randomColor();
891
+ courseDiv.onclick = () => showCourseDetails([course]);
892
+ courseDiv.style.cursor = "pointer";
893
+ intervalDiv.appendChild(courseDiv);
894
+ }
895
+
896
+ dayDiv.appendChild(intervalDiv);
897
+ });
898
+
899
+ scheduleContainer.appendChild(dayDiv);
900
+ });
901
+ })
902
+ .catch(error => console.error("加载课程表出错:", error));
903
+ }
904
+
905
+ function updateTimeHeader(weekStartDate) {
906
+ const timeHeader = document.getElementById("time-header");
907
+ const weekDates = generateWeekDates(weekStartDate);
908
+
909
+ // 清空表头后重新生成
910
+ timeHeader.innerHTML = `
911
+ <div>时间</div>
912
+ `;
913
+ weekDates.forEach((date, index) => {
914
+ const dayOfWeek = "一二三四五六日"[index];
915
+ const formattedDate = date.toLocaleDateString("zh-CN", { month: "2-digit", day: "2-digit" });
916
+
917
+ const headerDiv = document.createElement("div");
918
+ headerDiv.innerHTML = `
919
+ <span>${formattedDate}</span>
920
+ <br />
921
+ <span>周${dayOfWeek}</span>
922
+ `;
923
+ timeHeader.appendChild(headerDiv);
924
+ });
925
+ }
926
+
927
+ function generateWeekDates(startDate) {
928
+ const dates = [];
929
+ for (let i = 0; i < 7; i++) {
930
+ const date = new Date(startDate);
931
+ date.setDate(date.getDate() + i);
932
+ dates.push(date);
933
+ }
934
+ return dates;
935
+ }
936
+
937
+ function navigateToSection_01() {
938
+ window.location.href = "/";
939
+ }
940
+ function navigateToSection_02() {
941
+ window.location.href = "/teachers";
942
+ }
943
+ function randomColor() {
944
+ const colors = ["#ffcccb", "#c6e2ff", "#d5f5e3", "#f7dc6f", "#f1948a", "#aed6f1", "#d7bde2"];
945
+ return colors[Math.floor(Math.random() * colors.length)];
946
+ }
947
+
948
+ // 显示课程详情模态框
949
+ function showCourseDetails(courses) {
950
+ const modal = document.getElementById('courseModal');
951
+ const detailsContainer = document.getElementById('courseDetails');
952
+
953
+ // 清空之前的内容
954
+ detailsContainer.innerHTML = '';
955
+
956
+ // 为每个课程创建详情卡片
957
+ courses.forEach(course => {
958
+ const courseDetail = document.createElement('div');
959
+ courseDetail.className = 'course-detail';
960
+ courseDetail.innerHTML = `
961
+ <div class="course-name">${course.课程}</div>
962
+ <div class="course-info">教师: ${course.教师}</div>
963
+ <div class="course-info">班级: ${course.上课班级}</div>
964
+ <div class="course-info">时间: ${course.节次范围}</div>
965
+ <div class="course-info">地点: ${course.地点}</div>
966
+ <div class="course-info">校区: ${course.校区}</div>
967
+ <div class="course-info">周次: ${course.周次}</div>
968
+ `;
969
+ detailsContainer.appendChild(courseDetail);
970
+ });
971
+
972
+ // 显示模态框
973
+ modal.style.display = 'block';
974
+ }
975
+
976
+ // 关闭模态框
977
+ function closeModal() {
978
+ const modal = document.getElementById('courseModal');
979
+ modal.style.display = 'none';
980
+ }
981
+
982
+ // 点击模态框外部关闭
983
+ window.onclick = function(event) {
984
+ const modal = document.getElementById('courseModal');
985
+ if (event.target === modal) {
986
+ modal.style.display = 'none';
987
+ }
988
+ }
989
+
990
+ document.getElementById("week-select").addEventListener("change", loadSchedule);
991
+ document.getElementById("teacher-search").addEventListener("input", searchTeachers);
992
+
993
+ initializeWeeks();
994
+ loadTeachers();
995
+
996
+ // 鼠标轨迹粒子效果
997
+ class MouseTrailParticle {
998
+ constructor(x, y) {
999
+ this.x = x;
1000
+ this.y = y;
1001
+ this.vx = (Math.random() - 0.5) * 4;
1002
+ this.vy = (Math.random() - 0.5) * 4;
1003
+ this.life = 1.0;
1004
+ this.decay = 0.02 + Math.random() * 0.02;
1005
+ this.size = 2 + Math.random() * 4;
1006
+ this.color = `hsl(${Math.random() * 60 + 200}, 70%, 70%)`;
1007
+ }
1008
+
1009
+ update() {
1010
+ this.x += this.vx;
1011
+ this.y += this.vy;
1012
+ this.vx *= 0.98;
1013
+ this.vy *= 0.98;
1014
+ this.life -= this.decay;
1015
+ this.size *= 0.98;
1016
+ }
1017
+
1018
+ draw(ctx) {
1019
+ ctx.save();
1020
+ ctx.globalAlpha = this.life;
1021
+ ctx.fillStyle = this.color;
1022
+ ctx.shadowBlur = 10;
1023
+ ctx.shadowColor = this.color;
1024
+ ctx.beginPath();
1025
+ ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
1026
+ ctx.fill();
1027
+ ctx.restore();
1028
+ }
1029
+
1030
+ isDead() {
1031
+ return this.life <= 0 || this.size <= 0.1;
1032
+ }
1033
+ }
1034
+
1035
+ // 创建画布
1036
+ const canvas = document.createElement('canvas');
1037
+ canvas.style.position = 'fixed';
1038
+ canvas.style.top = '0';
1039
+ canvas.style.left = '0';
1040
+ canvas.style.width = '100%';
1041
+ canvas.style.height = '100%';
1042
+ canvas.style.pointerEvents = 'none';
1043
+ canvas.style.zIndex = '9999';
1044
+ document.body.appendChild(canvas);
1045
+
1046
+ const ctx = canvas.getContext('2d');
1047
+ const particles = [];
1048
+ let mouseX = 0;
1049
+ let mouseY = 0;
1050
+ let lastMouseX = 0;
1051
+ let lastMouseY = 0;
1052
+
1053
+ function resizeCanvas() {
1054
+ canvas.width = window.innerWidth;
1055
+ canvas.height = window.innerHeight;
1056
+ }
1057
+
1058
+ function addParticles(x, y) {
1059
+ const distance = Math.sqrt((x - lastMouseX) ** 2 + (y - lastMouseY) ** 2);
1060
+ if (distance > 5) {
1061
+ for (let i = 0; i < 3; i++) {
1062
+ particles.push(new MouseTrailParticle(x + (Math.random() - 0.5) * 10, y + (Math.random() - 0.5) * 10));
1063
+ }
1064
+ lastMouseX = x;
1065
+ lastMouseY = y;
1066
+ }
1067
+ }
1068
+
1069
+ function animate() {
1070
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1071
+
1072
+ // 更新和绘制粒子
1073
+ for (let i = particles.length - 1; i >= 0; i--) {
1074
+ const particle = particles[i];
1075
+ particle.update();
1076
+ particle.draw(ctx);
1077
+
1078
+ if (particle.isDead()) {
1079
+ particles.splice(i, 1);
1080
+ }
1081
+ }
1082
+
1083
+ requestAnimationFrame(animate);
1084
+ }
1085
+
1086
+ // 事件监听
1087
+ document.addEventListener('mousemove', (e) => {
1088
+ mouseX = e.clientX;
1089
+ mouseY = e.clientY;
1090
+ addParticles(mouseX, mouseY);
1091
+ });
1092
+
1093
+ window.addEventListener('resize', resizeCanvas);
1094
+
1095
+ // 初始化
1096
+ resizeCanvas();
1097
+ animate();
1098
+ </script>
1099
+ </body>
1100
+ </html>
templates/teacher.html ADDED
@@ -0,0 +1,712 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>教师课表</title>
7
+ <style>
8
+ /* 全局样式 */
9
+ body {
10
+ font-family: 'Roboto', Arial, sans-serif;
11
+ margin: 0;
12
+ padding: 0;
13
+ background-color: #f4f6f9;
14
+ color: #333;
15
+ }
16
+
17
+ h1 {
18
+ text-align: center;
19
+ margin: 20px 0;
20
+ font-size: 28px;
21
+ font-weight: bold;
22
+ color: #0078d4;
23
+ letter-spacing: 1.2px;
24
+ }
25
+
26
+ /* 控制区域样式 */
27
+ .controls {
28
+ display: flex;
29
+ justify-content: space-between;
30
+ align-items: center;
31
+ padding: 15px 20px;
32
+ background-color: #0078d4;
33
+ color: white;
34
+ border-radius: 10px;
35
+ margin: 10px 20px;
36
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
37
+ }
38
+
39
+ .controls > div {
40
+ flex: 1; /* 让每个子元素均匀分布 */
41
+ text-align: center; /* 居中对齐子元素 */
42
+ }
43
+
44
+ .nav-button {
45
+ padding: 10px 15px;
46
+ font-size: 14px;
47
+ border: none;
48
+ border-radius: 5px;
49
+ background-color: #fff;
50
+ color: #0078d4;
51
+ font-weight: bold;
52
+ cursor: pointer;
53
+ transition: all 0.3s ease;
54
+ }
55
+ .nav-button:hover {
56
+ background-color: #eaf4fc;
57
+ }
58
+ .nav-button:focus {
59
+ outline: none;
60
+ box-shadow: 0 0 5px rgba(0, 120, 212, 0.8);
61
+ }
62
+
63
+
64
+ .controls label {
65
+ font-weight: bold;
66
+ margin-right: 10px;
67
+ }
68
+
69
+ .controls select, .controls button {
70
+ padding: 8px 12px;
71
+ font-size: 14px;
72
+ border: none;
73
+ border-radius: 5px;
74
+ background-color: #fff;
75
+ color: #0078d4;
76
+ font-weight: bold;
77
+ cursor: pointer;
78
+ transition: all 0.3s ease;
79
+ }
80
+
81
+ .controls select:hover, .controls button:hover {
82
+ background-color: #eaf4fc;
83
+ }
84
+
85
+ .controls select:focus, .controls button:focus {
86
+ outline: none;
87
+ box-shadow: 0 0 5px rgba(0, 120, 212, 0.8);
88
+ }
89
+
90
+ /* 时间表头样式 */
91
+ .time-header {
92
+ display: grid;
93
+ grid-template-columns: 70px repeat(7, 1fr);
94
+ background: linear-gradient(135deg, #0078d4 0%, #0056a6 100%);
95
+ color: white;
96
+ text-align: center;
97
+ margin: 10px 20px;
98
+ font-size: 14px;
99
+ font-weight: bold;
100
+ padding: 10px 0;
101
+ border-radius: 8px;
102
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
103
+ }
104
+
105
+ .time-header div {
106
+ border-right: 1px solid #f0f0f0;
107
+ padding: 5px;
108
+ transition: transform 0.2s ease-in-out, background-color 0.2s; /* 添加平滑过渡效果 */
109
+ }
110
+
111
+ .time-header div:last-child {
112
+ border-right: none;
113
+ }
114
+
115
+ /* 鼠标悬停时样式 */
116
+ .time-header div:hover {
117
+ transform: translateY(-2px); /* 轻微上移 */
118
+ background-color: rgba(255, 255, 255, 0.2); /* 悬停背景高亮 */
119
+ cursor: pointer; /* 鼠标样式 */
120
+ }
121
+
122
+
123
+ /* 时间列样式 */
124
+ .time-column {
125
+ background-color: #f8f9fa;
126
+ text-align: center;
127
+ font-size: 14px;
128
+ display: flex;
129
+ flex-direction: column;
130
+ border-right: 1px solid #ddd;
131
+ }
132
+
133
+ .time-column div {
134
+ flex: 1;
135
+ padding: 10px 0;
136
+ border-top: 1px solid #ddd;
137
+ font-weight: bold;
138
+ }
139
+
140
+ .time-column div:first-child {
141
+ border-top: none;
142
+ }
143
+
144
+ /* 每日课程样式 */
145
+ .day {
146
+ background-color: white;
147
+ border: 1px solid #ddd;
148
+ padding: 5px;
149
+ height: 500px;
150
+ display: flex;
151
+ flex-direction: column;
152
+ position: relative;
153
+ border-radius: 0 0 8px 8px;
154
+ overflow: hidden;
155
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
156
+ }
157
+
158
+ .day:hover {
159
+ transform: translateY(-5px);
160
+ box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15);
161
+ }
162
+
163
+ /* 时间槽样式 */
164
+ .time-slot {
165
+ flex: 1;
166
+ position: relative;
167
+ border-top: 1px solid #eee;
168
+ overflow: hidden;
169
+ }
170
+
171
+ .time-slot:first-child {
172
+ border-top: none;
173
+ }
174
+
175
+ /* 课程样式 */
176
+ .time-slot .course {
177
+ position: absolute;
178
+ background-color: #a7d8de;
179
+ color: #333;
180
+ border-radius: 5px;
181
+ padding: 25px;
182
+ font-size: 15px;
183
+ font-weight: bold;
184
+ text-align: center;
185
+ margin: 5px;
186
+ width: 75%; /* 调整宽度为95% */
187
+ /*right: 2%; !* 调整位置,让它靠左边一些 *!*/
188
+ box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.1);
189
+ transition: transform 0.3s ease, background-color 0.3s ease;
190
+ height: 50%;
191
+ }
192
+
193
+
194
+ .search-container {
195
+
196
+
197
+ position: relative; /* 使搜索结果相对搜索框定位 */
198
+ }
199
+
200
+ /* 课程详情模态框样式 */
201
+ .modal {
202
+ display: none;
203
+ position: fixed;
204
+ z-index: 1000;
205
+ left: 0;
206
+ top: 0;
207
+ width: 100%;
208
+ height: 100%;
209
+ background-color: rgba(0, 0, 0, 0.5);
210
+ }
211
+
212
+ .modal-content {
213
+ background-color: #fefefe;
214
+ margin: 10% auto;
215
+ padding: 0;
216
+ border: none;
217
+ border-radius: 10px;
218
+ width: 80%;
219
+ max-width: 500px;
220
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
221
+ }
222
+
223
+ .modal-header {
224
+ background: linear-gradient(135deg, #0078d4 0%, #0056a6 100%);
225
+ color: white;
226
+ padding: 15px 20px;
227
+ border-radius: 10px 10px 0 0;
228
+ display: flex;
229
+ justify-content: space-between;
230
+ align-items: center;
231
+ }
232
+
233
+ .modal-title {
234
+ font-size: 18px;
235
+ font-weight: bold;
236
+ }
237
+
238
+ .close {
239
+ color: white;
240
+ font-size: 28px;
241
+ font-weight: bold;
242
+ cursor: pointer;
243
+ line-height: 1;
244
+ }
245
+
246
+ .close:hover {
247
+ opacity: 0.7;
248
+ }
249
+
250
+ .course-detail {
251
+ padding: 20px;
252
+ border-bottom: 1px solid #eee;
253
+ }
254
+
255
+ .course-detail:last-child {
256
+ border-bottom: none;
257
+ }
258
+
259
+ .course-name {
260
+ font-size: 16px;
261
+ font-weight: bold;
262
+ color: #0078d4;
263
+ margin-bottom: 10px;
264
+ }
265
+
266
+ .course-info {
267
+ margin: 5px 0;
268
+ color: #666;
269
+ }
270
+
271
+ .teacher-search {
272
+ width: 150px; /* 搜索框固定宽度 */
273
+ padding: 8px 12px;
274
+ font-size: 14px;
275
+ border: 1px solid #0078d4; /* 蓝色边框 */
276
+ border-radius: 5px;
277
+ color: #333;
278
+ box-sizing: border-box; /* 确保宽度包含内边距和边框 */
279
+ }
280
+
281
+ .search-results {
282
+ position: absolute; /* 使结果相对于搜索框容器定位 */
283
+ top: calc(100% + 4px); /* 紧贴搜索框下方,并增加 4px 间距 */
284
+ left: 43.5%; /* 确保左对齐 */
285
+ width: 150px; /* 与搜索框宽度一致 */
286
+ background: #f0f8ff; /* 浅蓝背景 */
287
+ border: 1px solid #0078d4; /* 与搜索框相同的蓝色边框 */
288
+ max-height: 150px; /* 最大高度 */
289
+ overflow-y: auto; /* 启用滚动条 */
290
+ z-index: 1000; /* 确保结果在顶层显示 */
291
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* 增加阴影 */
292
+ border-radius: 5px; /* 与搜索框一致的圆角 */
293
+ }
294
+
295
+ .search-results div {
296
+ padding: 8px 12px; /* 每个选项的内边距 */
297
+ font-size: 14px; /* 字体大小 */
298
+ color: #333; /* 默认文字颜色 */
299
+ cursor: pointer; /* 鼠标悬停时显示为手型 */
300
+ transition: background 0.3s, color 0.3s; /* 添加平滑过渡效果 */
301
+ }
302
+
303
+ .search-results div:hover {
304
+ background-color: #0078d4; /* 鼠标悬停时背景色 */
305
+ color: #fff; /* 悬停时文字颜色 */
306
+ }
307
+
308
+
309
+
310
+
311
+
312
+ /* 网格容器样式 */
313
+ .schedule-container {
314
+ display: grid;
315
+ grid-template-columns: 70px repeat(7, 1fr);
316
+ gap: 2px;
317
+ margin: 10px 20px;
318
+ background-color: #fff;
319
+ border-radius: 10px;
320
+ overflow: hidden;
321
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
322
+ }
323
+ @media (max-width: 768px) {
324
+ body {
325
+ font-size: 14px;
326
+ padding: 5px;
327
+ }
328
+ h1 {
329
+ font-size: 20px;
330
+ margin: 15px 0;
331
+ }
332
+ .controls {
333
+ display: flex;
334
+ flex-direction: row; /* 保持在一行内 */
335
+ flex-wrap: nowrap; /* 禁止换行 */
336
+ justify-content: space-around; /* 均匀分布各个控件 */
337
+ align-items: center; /* 纵向居中 */
338
+ padding: 10px 5px; /* 增加一些内边距,使其不显得太拥挤 */
339
+ margin: 5px;
340
+ gap: 5px; /* 控制组���之间的间距 */
341
+ }
342
+ .controls select,
343
+ .controls button {
344
+ padding: 6px; /* 适当减少内边距以适应小屏幕 */
345
+ font-size: 12px; /* 调整字体大小适应小屏幕 */
346
+ margin: 2px;
347
+ min-width: 80px; /* 设置最小宽度,确保控件在小屏幕上仍可点击 */
348
+ }
349
+
350
+ .time-header {
351
+ grid-template-columns: 25px repeat(7, 1fr);
352
+ font-size: 12px; /* 调整字体大小以适合移动端 */
353
+ padding: 12px 5px; /* 增加上下的内边距使其看起来更高 */
354
+ width: 95%;
355
+ margin: 10px 5px;
356
+ }
357
+ .schedule-container {
358
+ grid-template-columns: 22px repeat(7, minmax(0, 1fr));
359
+ margin: 3px;
360
+ display: grid;
361
+ gap: 1px;
362
+
363
+ background-color: #fff;
364
+ border-radius: 10px;
365
+ overflow: hidden;
366
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
367
+
368
+ }
369
+ .day {
370
+ padding: 3px;
371
+ border: 1px solid #ccc;
372
+ border-radius: 5px;
373
+ }
374
+ .time-column {
375
+ grid-column: 1;
376
+ text-align: center;
377
+ font-size: 15px;
378
+ display: flex;
379
+ flex-direction: column;
380
+ justify-content: center;
381
+ line-height: 1.2;
382
+
383
+ }
384
+ .time-slot .course {
385
+ font-size: 12px;
386
+ padding: 1px;
387
+ /*height: auto;*/
388
+ width: auto;
389
+ word-break: break-word; /* 确保长单词在小屏幕上也会换行 */
390
+ white-space: normal; /* 允许文本换行 */
391
+ overflow-wrap: break-word; /* 处理可能过长的文本,强制其换行 */
392
+ margin: 1px;
393
+ height: 90%;
394
+ }
395
+ .teacher-search {
396
+ width: 100%; /* 搜索框固定宽度 */
397
+ padding: 8px 12px;
398
+ font-size: 14px;
399
+ border: 1px solid #0078d4; /* 蓝色边框 */
400
+ border-radius: 5px;
401
+ color: #333;
402
+ box-sizing: border-box; /* 确保宽度包含内边距和边框 */
403
+ }
404
+ .search-results {
405
+ position: absolute;
406
+ top: calc(100% + 2px); /* 列表紧贴搜索框下方,留 2px 的间隙 */
407
+ left: 0%; /* 与搜索框左侧完全对齐 */
408
+ width: 100%; /* 列表宽度与搜索框保持一致 */
409
+ background: #f0f8ff; /* 浅蓝背景 */
410
+ border: 1px solid #0078d4; /* 边框颜色 */
411
+ max-height: 150px; /* 最大高度,超出部分滚动 */
412
+ overflow-y: auto; /* 启用垂直滚动条 */
413
+ z-index: 1000; /* 确保显示在最上层 */
414
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
415
+ border-radius: 5px; /* 圆角边框 */
416
+ box-sizing: border-box; /* 确保宽度计算准确 */
417
+ }
418
+ }
419
+ </style>
420
+ </head>
421
+ <body>
422
+ <h1>教师课表</h1>
423
+ <div class="controls">
424
+
425
+
426
+ <div class="search-container">
427
+ <label for="teacher-search">教师:</label>
428
+ <input type="text" id="teacher-search" class="teacher-search" placeholder="搜索教师">
429
+ <div id="search-results" class="search-results"></div>
430
+ </div>
431
+
432
+ <div>
433
+ <button class="nav-button" onclick="window.location.href='/'">班级查询</button>
434
+ <button class="nav-button" onclick="window.location.href='/students'">学生查询</button>
435
+ <button class="nav-button" onclick="window.location.href='/classrooms'">教室查询</button>
436
+ </div>
437
+ <div>
438
+ <label for="week-select">周次:</label>
439
+ <select id="week-select"></select>
440
+ </div>
441
+ </div>
442
+
443
+ <div class="time-header" id="time-header">
444
+ <div>时间</div>
445
+ <div>周一</div>
446
+ <div>周二</div>
447
+ <div>周三</div>
448
+ <div>周四</div>
449
+ <div>周五</div>
450
+ <div>周六</div>
451
+ <div>周日</div>
452
+ </div>
453
+
454
+ <div class="schedule-container" id="schedule">
455
+ <div class="time-column">
456
+ <div>1-3节</div>
457
+ <div>4-5节</div>
458
+ <div>6-8节</div>
459
+ <div>9-11节</div>
460
+ </div>
461
+ </div>
462
+
463
+ <!-- 课程详情模态框 -->
464
+ <div id="courseModal" class="modal">
465
+ <div class="modal-content">
466
+ <div class="modal-header">
467
+ <div class="modal-title">课程详情</div>
468
+ <span class="close" onclick="closeModal()">&times;</span>
469
+ </div>
470
+ <div id="courseDetails">
471
+ <!-- 课程详情将在这里动态生成 -->
472
+ </div>
473
+ </div>
474
+ </div>
475
+
476
+ <script>
477
+ let currentWeek = getCurrentWeek(); // 使用 getCurrentWeek() 函数获取当前周次
478
+ let teacherList = [];
479
+ function initializeWeeks() {
480
+ const weekSelect = document.getElementById("week-select");
481
+ for (let i = 1; i <= 18; i++) {
482
+ const option = document.createElement("option");
483
+ option.value = i;
484
+ option.textContent = `第${i}周`;
485
+ if (i === currentWeek) {
486
+ option.selected = true; // 默认选中当前周次
487
+ }
488
+ weekSelect.appendChild(option);
489
+ }
490
+ }
491
+ function getCurrentWeek() {
492
+ const firstWeekStartDate = new Date("2024-09-02"); // 假设第一周从2024年9月2日(周一)开始
493
+ const today = new Date();
494
+ const timeDifference = today - firstWeekStartDate;
495
+ const daysDifference = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
496
+ return Math.floor(daysDifference / 7) + 1; // 根据天数计算当前周次
497
+ }
498
+ function loadTeachers() {
499
+ fetch("/api/teachers")
500
+ .then(response => response.json())
501
+ .then(teachers => {
502
+ teacherList = teachers.sort((a, b) => {
503
+ if (a === "秦振凯") return -1;
504
+ if (b === "秦振凯") return 1;
505
+ return a.localeCompare(b);
506
+ });
507
+ })
508
+ .catch(error => console.error("加载教师列表出错:", error));
509
+ }
510
+
511
+ function searchTeachers() {
512
+ const searchInput = document.getElementById("teacher-search").value.toLowerCase();
513
+ const searchResults = document.getElementById("search-results");
514
+ searchResults.innerHTML = "";
515
+
516
+ if (searchInput.trim() === "") {
517
+ return;
518
+ }
519
+
520
+ const filteredTeachers = teacherList.filter(teacher =>
521
+ teacher.toLowerCase().includes(searchInput)
522
+ );
523
+
524
+ filteredTeachers.forEach(teacher => {
525
+ const resultDiv = document.createElement("div");
526
+ resultDiv.textContent = teacher;
527
+ resultDiv.onclick = () => selectTeacher(teacher);
528
+ searchResults.appendChild(resultDiv);
529
+ });
530
+ }
531
+
532
+ function selectTeacher(teacher) {
533
+ document.getElementById("teacher-search").value = teacher;
534
+ document.getElementById("search-results").innerHTML = "";
535
+ loadSchedule();
536
+ }
537
+
538
+ function loadSchedule() {
539
+ const teacherSearch = document.getElementById("teacher-search").value;
540
+ const weekSelect = document.getElementById("week-select");
541
+ const selectedWeek = parseInt(weekSelect.value);
542
+ const apiUrl = `/api/teacher_courses?week=${selectedWeek}&teacher=${encodeURIComponent(teacherSearch)}`;
543
+
544
+ // 先清空之前的表头和课程表内容,避免混淆
545
+ document.getElementById("time-header").innerHTML = '';
546
+ document.getElementById("schedule").innerHTML = `
547
+ <div class='time-column'>
548
+ <div>1-3节</div>
549
+ <div>4-5节</div>
550
+ <div>6-8节</div>
551
+ <div>9-11节</div>
552
+ </div>
553
+ `;
554
+
555
+ // 获取所选周次的起始日期
556
+ const firstWeekStartDate = new Date("2024-09-02"); // 假设第一周从2024年9月2日(周一)开始
557
+ const selectedWeekStartDate = new Date(firstWeekStartDate);
558
+ selectedWeekStartDate.setDate(firstWeekStartDate.getDate() + (selectedWeek - 1) * 7);
559
+
560
+ // 更新时间表头
561
+ updateTimeHeader(selectedWeekStartDate);
562
+
563
+ fetch(apiUrl)
564
+ .then(response => response.json())
565
+ .then(data => {
566
+ if (data.length === 0) {
567
+ console.warn("未获取到课程数据");
568
+ return;
569
+ }
570
+
571
+ // 初始化空白课程表
572
+ const groupedByDay = Array.from({ length: 7 }, () => ({
573
+ intervals: Array.from({ length: 4 }, () => [])
574
+ }));
575
+
576
+ // 填充课程
577
+ data.forEach(course => {
578
+ const dayIndex = course.星期 - 1; // 星期一为0,星期日为6
579
+ const startPeriod = course.节次[0];
580
+
581
+ let intervalIndex = 0;
582
+ if (startPeriod >= 1 && startPeriod <= 3) intervalIndex = 0;
583
+ else if (startPeriod >= 4 && startPeriod <= 5) intervalIndex = 1;
584
+ else if (startPeriod >= 6 && startPeriod <= 8) intervalIndex = 2;
585
+ else if (startPeriod >= 9 && startPeriod <= 11) intervalIndex = 3;
586
+
587
+ groupedByDay[dayIndex].intervals[intervalIndex].push(course);
588
+ });
589
+
590
+ // 渲染课程表
591
+ const scheduleContainer = document.getElementById("schedule");
592
+ groupedByDay.forEach((dayData, dayIndex) => {
593
+ const dayDiv = document.createElement("div");
594
+ dayDiv.className = "day";
595
+
596
+ dayData.intervals.forEach((intervalCourses, intervalIndex) => {
597
+ const intervalDiv = document.createElement("div");
598
+ intervalDiv.className = "time-slot";
599
+
600
+ if (intervalCourses.length > 0) {
601
+ const course = intervalCourses[0];
602
+ const courseDiv = document.createElement("div");
603
+ courseDiv.className = "course";
604
+ courseDiv.innerHTML = `${course.课程} ${course.校区} ${course.地点}`;
605
+ courseDiv.style.backgroundColor = randomColor();
606
+ courseDiv.onclick = () => showCourseDetails([course]);
607
+ courseDiv.style.cursor = "pointer";
608
+ intervalDiv.appendChild(courseDiv);
609
+ }
610
+
611
+ dayDiv.appendChild(intervalDiv);
612
+ });
613
+
614
+ scheduleContainer.appendChild(dayDiv);
615
+ });
616
+ })
617
+ .catch(error => console.error("加载课程表出错:", error));
618
+ }
619
+
620
+ function updateTimeHeader(weekStartDate) {
621
+ const timeHeader = document.getElementById("time-header");
622
+ const weekDates = generateWeekDates(weekStartDate);
623
+
624
+ // 清空表头后重新生成
625
+ timeHeader.innerHTML = `
626
+ <div>时间</div>
627
+ `;
628
+ weekDates.forEach((date, index) => {
629
+ const dayOfWeek = "一二三四五六日"[index];
630
+ const formattedDate = date.toLocaleDateString("zh-CN", { month: "2-digit", day: "2-digit" });
631
+
632
+ const headerDiv = document.createElement("div");
633
+ headerDiv.innerHTML = `
634
+ <span>${formattedDate}</span>
635
+ <br />
636
+ <span>周${dayOfWeek}</span>
637
+ `;
638
+ timeHeader.appendChild(headerDiv);
639
+ });
640
+ }
641
+
642
+ function generateWeekDates(startDate) {
643
+ const dates = [];
644
+ for (let i = 0; i < 7; i++) {
645
+ const date = new Date(startDate);
646
+ date.setDate(date.getDate() + i);
647
+ dates.push(date);
648
+ }
649
+ return dates;
650
+ }
651
+
652
+ function navigateToSection_01() {
653
+ window.location.href = "/";
654
+ }
655
+ function navigateToSection_02() {
656
+ window.location.href = "/students";
657
+ }
658
+ function randomColor() {
659
+ const colors = ["#ffcccb", "#c6e2ff", "#d5f5e3", "#f7dc6f", "#f1948a", "#aed6f1", "#d7bde2"];
660
+ return colors[Math.floor(Math.random() * colors.length)];
661
+ }
662
+
663
+ // 显示课程详情模态框
664
+ function showCourseDetails(courses) {
665
+ const modal = document.getElementById('courseModal');
666
+ const detailsContainer = document.getElementById('courseDetails');
667
+
668
+ // 清空之前的内容
669
+ detailsContainer.innerHTML = '';
670
+
671
+ // 为每个课程创建详情卡片
672
+ courses.forEach(course => {
673
+ const courseDetail = document.createElement('div');
674
+ courseDetail.className = 'course-detail';
675
+ courseDetail.innerHTML = `
676
+ <div class="course-name">${course.课程}</div>
677
+ <div class="course-info">教师: ${course.教师}</div>
678
+ <div class="course-info">班级: ${course.上课班级}</div>
679
+ <div class="course-info">时间: ${course.节次范围}</div>
680
+ <div class="course-info">地点: ${course.地点}</div>
681
+ <div class="course-info">校区: ${course.校区}</div>
682
+ <div class="course-info">周次: ${course.周次}</div>
683
+ `;
684
+ detailsContainer.appendChild(courseDetail);
685
+ });
686
+
687
+ // 显示模态框
688
+ modal.style.display = 'block';
689
+ }
690
+
691
+ // 关闭模态框
692
+ function closeModal() {
693
+ const modal = document.getElementById('courseModal');
694
+ modal.style.display = 'none';
695
+ }
696
+
697
+ // 点击模态框外部关闭
698
+ window.onclick = function(event) {
699
+ const modal = document.getElementById('courseModal');
700
+ if (event.target === modal) {
701
+ modal.style.display = 'none';
702
+ }
703
+ }
704
+
705
+ document.getElementById("week-select").addEventListener("change", loadSchedule);
706
+ document.getElementById("teacher-search").addEventListener("input", searchTeachers);
707
+
708
+ initializeWeeks();
709
+ loadTeachers();
710
+ </script>
711
+ </body>
712
+ </html>