class123 / templates /schedule_overlap.html
ikun520's picture
Upload 5 files
32b55b9 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>课表叠加查询</title>
<style>
/* 全局样式 */
body {
font-family: 'Roboto', Arial, sans-serif;
margin: 0;
padding: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 25%, #f093fb 50%, #f5576c 75%, #4facfe 100%);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
color: #333;
position: relative;
overflow-x: hidden;
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
z-index: -1;
pointer-events: none;
}
h1 {
text-align: center;
color: #fff;
font-size: 28px;
margin: 20px 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
font-weight: 700;
}
.controls {
display: flex;
justify-content: center;
align-items: center;
margin: 20px;
padding: 15px;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(15px);
border-radius: 15px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
gap: 20px;
flex-wrap: wrap;
}
.controls label {
font-weight: bold;
color: #fff;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
}
.controls select,
.controls button {
padding: 10px 15px;
font-size: 14px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 8px;
background: rgba(255, 255, 255, 0.9);
color: #333;
font-weight: bold;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
backdrop-filter: blur(10px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.controls select:hover,
.controls button:hover {
background: rgba(255, 255, 255, 0.95);
transform: translateY(-1px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.nav-button {
background: rgba(255, 255, 255, 0.2) !important;
color: #fff !important;
border: 1px solid rgba(255, 255, 255, 0.3) !important;
}
.nav-button:hover {
background: rgba(255, 255, 255, 0.3) !important;
}
.class-legend {
display: flex;
justify-content: center;
align-items: center;
margin: 20px;
padding: 15px;
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(15px);
border-radius: 15px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
gap: 20px;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
color: #fff;
font-weight: bold;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
}
.legend-color {
width: 20px;
height: 20px;
border-radius: 4px;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.time-header {
display: grid;
grid-template-columns: 80px repeat(7, 1fr);
gap: 2px;
margin: 20px;
padding: 15px;
background: rgba(255, 255, 255, 0.9);
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
font-weight: bold;
text-align: center;
}
.schedule-container {
display: grid;
grid-template-columns: 80px repeat(7, 1fr);
gap: 2px;
margin: 20px;
background: rgba(255, 255, 255, 0.9);
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.time-column {
display: flex;
flex-direction: column;
background: #f8f9fa;
font-weight: bold;
text-align: center;
}
.time-slot {
padding: 10px 5px;
border-bottom: 1px solid #ddd;
display: flex;
align-items: center;
justify-content: center;
height: 80px; /* 固定高度而不是最小高度 */
font-size: 12px;
}
.day {
display: flex;
flex-direction: column;
}
.day-slot {
height: 90px;
border-bottom: 1px solid #ddd;
position: relative;
padding: 5px;
display: flex;
flex-direction: column;
gap: 1px;
overflow: hidden;
}
.course {
background: rgba(167, 216, 222, 0.8);
color: #333;
border-radius: 4px;
padding: 10px 5px;
font-size: 16px;
font-weight: bold;
text-align: center;
margin: 1px;
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid rgba(0, 0, 0, 0.1);
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
height: 80px;
overflow: hidden; /* 防止文本溢出 */
word-wrap: break-word; /* 长文本换行 */
}
.course:hover {
transform: scale(1.05);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
z-index: 10;
}
.course-23 { background: rgba(255, 182, 193, 0.8); }
.course-24-1 { background: rgba(173, 216, 230, 0.8); }
.course-24-2 { background: rgba(144, 238, 144, 0.8); }
.course-24-3 { background: rgba(255, 218, 185, 0.8); }
.course-24-info { background: rgba(221, 160, 221, 0.8); }
.free-slot {
background: rgba(144, 238, 144, 0.3);
color: #2d5a2d;
border-radius: 4px;
padding: 4px;
font-size: 14px;
font-weight: bold;
text-align: center;
margin: 1px;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed #90ee90;
min-height: 0;
word-wrap: break-word;
white-space: normal;
line-height: 1.2;
}
/* 模态框样式 */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(5px);
}
.modal-content {
background: linear-gradient(135deg, #fff 0%, #f8f9fa 100%);
margin: 5% auto;
padding: 0;
border-radius: 15px;
width: 90%;
max-width: 600px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
animation: modalSlideIn 0.3s ease-out;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: translateY(-50px) scale(0.9);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.modal-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 15px 15px 0 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-title {
font-size: 20px;
font-weight: bold;
margin: 0;
}
.close {
color: white;
font-size: 28px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
}
.close:hover {
transform: scale(1.1);
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
#courseDetails {
padding: 20px;
max-height: 400px;
overflow-y: auto;
}
.course-info {
margin: 10px 0;
padding: 10px;
background: rgba(0, 0, 0, 0.05);
border-radius: 8px;
border-left: 4px solid #667eea;
}
/* 移动端适配 */
@media (max-width: 768px) {
body {
font-size: 14px;
padding: 5px;
}
h1 {
font-size: 20px;
margin: 15px 0;
}
.controls {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px;
margin: 5px;
}
.controls div {
flex: none;
width: 100%;
text-align: center;
}
.controls select,
.controls button {
padding: 8px 12px;
font-size: 14px;
margin: 2px;
min-width: 80px;
}
.time-header {
grid-template-columns: 50px repeat(7, minmax(0, 1fr));
margin: 5px;
gap: 1px;
font-size: 12px;
padding: 12px 5px;
}
.schedule-container {
grid-template-columns: 50px repeat(7, minmax(0, 1fr));
margin: 5px;
gap: 1px;
display: grid;
background-color: #fff;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.day {
padding: 3px;
border: 1px solid #ccc;
border-radius: 5px;
}
.time-column {
grid-column: 1;
text-align: center;
font-size: 12px;
padding: 15px 2px;
display: flex;
flex-direction: column;
justify-content: center;
line-height: 1.2;
}
.time-slot {
height: 152px;
font-size: 12px;
padding: 3px;
min-height: 100px;
}
.day-slot {
min-height: 160px;
padding: 0px;
}
.course {
font-size: 14px;
padding: 0px;
margin: 1px;
height: auto;
width: auto;
word-break: break-word;
white-space: normal;
overflow-wrap: break-word;
}
.free-slot {
background: rgba(144, 238, 144, 0.3);
color: #2d5a2d;
border-radius: 4px;
padding: 0px;
font-size: 14px;
font-weight: bold;
text-align: center;
margin: 0px;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed #90ee90;
min-height: 0;
word-wrap: break-word;
white-space: normal;
line-height: 1.1;
}
.course strong {
font-size: 11px;
}
.course .teacher,
.course .class,
.course .period {
font-size: 11px;
}
/* 移动端模态框样式 */
.modal-content {
width: 95%;
margin: 5% auto;
padding: 15px;
}
.modal-title {
font-size: 18px;
}
.course-detail {
margin: 8px 0;
padding: 8px;
}
.course-detail .course-name {
font-size: 16px;
}
.course-detail .course-info {
font-size: 14px;
}
}
/* 课程详情样式 */
.course-name {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 10px;
text-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
}
.course-info {
margin: 8px 0;
padding: 5px 0;
color: #555;
border-bottom: 1px solid #eee;
text-shadow: 0 0 5px rgba(255, 255, 255, 0.2);
}
.course-info:last-child {
border-bottom: none;
}
.course-section {
background: #f8f9fa;
border-radius: 8px;
padding: 15px;
margin: 10px 0;
border-left: 4px solid #007bff;
display: flex;
flex-direction: column;
min-height: 120px;
}
/* 确保课程内容在固定高度内正确显示 */
.course .course-name {
font-size: 9px;
line-height: 1.1;
margin-bottom: 2px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.course .course-info {
font-size: 8px;
line-height: 1.0;
margin: 1px 0;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.free-section {
background: #d4edda;
border-radius: 8px;
padding: 15px;
margin: 10px 0;
border-left: 4px solid #28a745;
display: flex;
flex-direction: column;
min-height: 100px;
}
.student-list {
background: #fff;
border-radius: 5px;
padding: 10px;
margin-top: auto;
border: 1px solid #dee2e6;
font-size: 14px;
line-height: 1.4;
flex-grow: 1;
}
.course-info {
margin: 3px 0;
line-height: 1.4;
}
.course-name {
font-weight: bold;
font-size: 1.1em;
margin-bottom: 8px;
padding-bottom: 5px;
border-bottom: 1px solid #eee;
}
</style>
</head>
<body>
<h1>大数据专业课表叠加查询</h1>
<div class="controls">
<div>
<label for="week-select">周次:</label>
<select id="week-select"></select>
</div>
<div>
<button class="nav-button" onclick="window.location.href='/'">返回主页</button>
<button class="nav-button" onclick="window.location.href='/students'">学生查询</button>
<button class="nav-button" onclick="window.location.href='/teachers'">教师查询</button>
<button class="nav-button" onclick="window.location.href='/classrooms'">教室查询</button>
</div>
</div>
<div class="time-header" id="time-header">
<div>时间</div>
<div><span>09/02</span><br><span>周一</span></div>
<div><span>09/03</span><br><span>周二</span></div>
<div><span>09/04</span><br><span>周三</span></div>
<div><span>09/05</span><br><span>周四</span></div>
<div><span>09/06</span><br><span>周五</span></div>
<div><span>09/07</span><br><span>周六</span></div>
<div><span>09/08</span><br><span>周日</span></div>
</div>
<div class="schedule-container" id="schedule">
<div class="time-column">
<div class="time-slot">1-3节</div>
<div class="time-slot">4-5节</div>
<div class="time-slot">6-8节</div>
<div class="time-slot">9-11节</div>
</div>
</div>
<!-- 课程详情模态框 -->
<div id="courseModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">时间段详情</div>
<span class="close" onclick="closeModal()">&times;</span>
</div>
<div id="courseDetails">
<!-- 课程详情将在这里动态生成 -->
</div>
</div>
</div>
<script>
let currentWeek = getCurrentWeek();
function initializeWeeks() {
const weekSelect = document.getElementById("week-select");
for (let i = 1; i <= 18; i++) {
const option = document.createElement("option");
option.value = i;
option.textContent = `第${i}周`;
if (i === currentWeek) {
option.selected = true;
}
weekSelect.appendChild(option);
}
}
function getCurrentWeek() {
const firstWeekStartDate = new Date("2025-09-01");
const today = new Date();
const timeDifference = today - firstWeekStartDate;
const daysDifference = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
return Math.floor(daysDifference / 7) + 1;
}
function updateTimeHeader(firstDayDate) {
const timeHeader = document.getElementById("time-header");
const dayNames = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
// 确保从周一开始计算
const mondayDate = new Date(firstDayDate);
const dayOfWeek = mondayDate.getDay(); // 0=周日, 1=周一, ..., 6=周六
const daysToMonday = dayOfWeek === 0 ? -6 : 1 - dayOfWeek; // 计算到周一的天数差
mondayDate.setDate(mondayDate.getDate() + daysToMonday);
for (let i = 1; i <= 7; i++) {
const dayDate = new Date(mondayDate);
dayDate.setDate(mondayDate.getDate() + i - 1);
const dayDiv = timeHeader.children[i];
dayDiv.innerHTML = `<span>${(dayDate.getMonth() + 1).toString().padStart(2, '0')}/${dayDate.getDate().toString().padStart(2, '0')}</span><br><span>${dayNames[i - 1]}</span>`;
}
}
function getClassColor(className) {
if (className === "23大数据1区") return "course-23";
if (className === "24大数据1区") return "course-24-1";
if (className === "24大数据2区") return "course-24-2";
if (className === "24大数据3区") return "course-24-3";
if (className === "24信息安全技术应用1区") return "course-24-info";
return "course";
}
function formatFreeClasses(freeClasses) {
if (freeClasses.length === 0) return '';
// 如果全部班级都没课
const allClasses = ["23大数据1区", "24大数据1区", "24大数据2区", "24大数据3区", "24信息安全技术应用1区"];
if (freeClasses.length === allClasses.length) {
return '实验室都没课';
}
// 分离不同类型的班级
const class23 = freeClasses.filter(cls => cls.includes('23'));
const class24Data = freeClasses.filter(cls => cls.includes('24') && cls.includes('大数据'));
const class24Info = freeClasses.filter(cls => cls.includes('24') && cls.includes('信息安全'));
let result = [];
// 处理23级
if (class23.length > 0) {
result.push(class23.join('、'));
}
// 处理24级大数据专业
if (class24Data.length > 0) {
if (class24Data.length >= 3) {
result.push('24级大数据全区');
} else if (class24Data.length === 2) {
const regions = class24Data.map(cls => cls.match(/\d+区/)[0]);
result.push(`24级大数据${regions.join('、')}`);
} else {
result.push(class24Data[0]);
}
}
// 处理24级信息安全专业
if (class24Info.length > 0) {
result.push(class24Info.join('、'));
}
return result.join('、');
}
function loadScheduleOverlap() {
const selectedWeek = document.getElementById("week-select").value;
const apiUrl = `/api/schedule_overlap?week=${selectedWeek}`;
fetch(apiUrl)
.then(response => response.json())
.then(data => {
const scheduleContainer = document.getElementById("schedule");
// 清空现有内容,保留时间列
scheduleContainer.innerHTML = `
<div class='time-column'>
<div class='time-slot'>1-3节</div>
<div class='time-slot'>4-5节</div>
<div class='time-slot'>6-8节</div>
<div class='time-slot'>9-11节</div>
</div>
`;
// 更新时间头
const firstDayDate = new Date(data[0]?.日期 || "2025-09-01");
updateTimeHeader(firstDayDate);
// 初始化课程表数据结构
const scheduleData = {};
for (let day = 1; day <= 7; day++) {
scheduleData[day] = {
1: [], // 1-3节
2: [], // 4-5节
3: [], // 6-8节
4: [] // 9-11节
};
}
// 填充课程数据
data.forEach(course => {
const day = course.星期;
const startPeriod = course.节次[0];
let timeSlot = 1;
if (startPeriod >= 1 && startPeriod <= 3) timeSlot = 1;
else if (startPeriod >= 4 && startPeriod <= 5) timeSlot = 2;
else if (startPeriod >= 6 && startPeriod <= 8) timeSlot = 3;
else if (startPeriod >= 9 && startPeriod <= 11) timeSlot = 4;
scheduleData[day][timeSlot].push(course);
});
// 渲染课程表
for (let day = 1; day <= 7; day++) {
const dayDiv = document.createElement("div");
dayDiv.className = "day";
for (let timeSlot = 1; timeSlot <= 4; timeSlot++) {
const slotDiv = document.createElement("div");
slotDiv.className = "day-slot";
const courses = scheduleData[day][timeSlot];
// 获取所有目标班级
const allClasses = ["23大数据1区", "24大数据1区", "24大数据2区", "24大数据3区", "24信息安全技术应用1区"];
const classesWithCourse = courses.map(c => c.班级);
const freeClasses = allClasses.filter(cls => !classesWithCourse.includes(cls));
if (freeClasses.length > 0) {
// 显示没有课的区队(合并显示)
const freeDiv = document.createElement("div");
freeDiv.className = "free-slot";
const freeText = formatFreeClasses(freeClasses);
// 如果已经包含"没课"字样,就不再添加
if (freeText.includes('没课')) {
freeDiv.innerHTML = freeText;
} else {
freeDiv.innerHTML = freeText + "没课";
}
freeDiv.onclick = () => showTimeSlotDetails(courses, freeClasses, day, timeSlot);
slotDiv.appendChild(freeDiv);
} else {
// 所有班级都有课
const busyDiv = document.createElement("div");
busyDiv.className = "course";
busyDiv.innerHTML = "全部有课";
busyDiv.onclick = () => showTimeSlotDetails(courses, freeClasses, day, timeSlot);
slotDiv.appendChild(busyDiv);
}
dayDiv.appendChild(slotDiv);
}
scheduleContainer.appendChild(dayDiv);
}
})
.catch(error => {
console.error('加载课表数据失败:', error);
alert('加载课表数据失败,请稍后重试');
});
}
// 缓存学生数据,避免重复请求
const studentCache = new Map();
async function getStudentList(className) {
if (studentCache.has(className)) {
return studentCache.get(className);
}
try {
const response = await fetch(`/api/class_students?class_name=${encodeURIComponent(className)}`);
const students = await response.json();
studentCache.set(className, students);
return students;
} catch (error) {
console.error('获取学生名单失败:', error);
return [];
}
}
async function showTimeSlotDetails(courses, freeClasses, day, timeSlot) {
const dayNames = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
const timeSlotNames = ["1-3节", "4-5节", "6-8节", "9-11节"];
let detailsHtml = `<h3>${dayNames[day-1]} ${timeSlotNames[timeSlot-1]}</h3>`;
// 显示空闲的班级及其学生信息
if (freeClasses.length > 0) {
detailsHtml += `<h4 style="color: #28a745; margin-bottom: 15px;">🟢 没有课的区队:</h4>`;
for (const freeClass of freeClasses) {
const students = await getStudentList(freeClass);
detailsHtml += `
<div class="free-section">
<div class="course-name" style="color: #28a745;">${freeClass}</div>
<div class="student-list">
<strong>空闲学生名单:</strong><br>
${students.length > 0 ? students.join('、') : '暂无学生信息'}
</div>
</div>
`;
}
}
// 显示有课的班级详细信息
if (courses.length > 0) {
detailsHtml += `<h4 style="color: #dc3545; margin-top: 20px; margin-bottom: 15px;">📚 有课的区队:</h4>`;
// 按班级分组,每个区队显示一个框
const classCourses = {};
courses.forEach(course => {
if (!classCourses[course.班级]) {
classCourses[course.班级] = [];
}
classCourses[course.班级].push(course);
});
for (const [className, classCourseList] of Object.entries(classCourses)) {
// 获取该班级的学生名单(使用缓存)
const students = await getStudentList(className);
// 构建课程列表(只显示课程名称,合并成一行)
const courseNames = classCourseList.map(course => {
// 去除课程编号和所有括号,只保留课程名称
let courseName = course.课程;
// 匹配并去除 [编号][课程名称] 格式,提取课程名称
const match = courseName.match(/\[[^\]]+\]\[([^\]]+)\]/);
if (match) {
courseName = match[1]; // 提取第二个括号内的内容
} else {
// 如果格式不匹配,尝试去除所有方括号
courseName = courseName.replace(/\[[^\]]*\]/g, '');
}
// 去除末尾的数字标识,如 -1、-2 等
courseName = courseName.replace(/-\d+$/, '');
return courseName.trim();
});
const uniqueCourseNames = [...new Set(courseNames)]; // 去重
// 限制显示前三个课程,如果超过三个则显示"等X门课程"
let courseListHtml;
if (uniqueCourseNames.length <= 3) {
courseListHtml = uniqueCourseNames.join('、');
} else {
const firstThree = uniqueCourseNames.slice(0, 3);
courseListHtml = firstThree.join('、') + `等${uniqueCourseNames.length}门课程`;
}
detailsHtml += `
<div class="course-section">
<div class="course-name">${className}</div>
<div class="course-info">
${courseListHtml}
</div>
<div class="student-list">
<strong>学生名单:</strong><br>
${students.length > 0 ? students.join('、') : '暂无学生信息'}
</div>
</div>
`;
}
}
document.getElementById("courseDetails").innerHTML = detailsHtml;
document.getElementById("courseModal").style.display = "block";
}
function closeModal() {
document.getElementById("courseModal").style.display = "none";
}
// 点击模态框外部关闭
window.onclick = function(event) {
const modal = document.getElementById("courseModal");
if (event.target === modal) {
modal.style.display = "none";
}
}
// 事件监听
document.getElementById("week-select").addEventListener("change", loadScheduleOverlap);
// 初始化
initializeWeeks();
loadScheduleOverlap();
</script>
</body>
</html>