ikun520 commited on
Commit
32b55b9
·
verified ·
1 Parent(s): 629f016

Upload 5 files

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