更新各班级出勤登记与课评汇总,新增教学日程查询技能与CSP03枚举算法教案

This commit is contained in:
chengzi
2026-05-05 18:53:11 +08:00
parent 1f276f874b
commit b7cd74392c
447 changed files with 57291 additions and 148 deletions

View File

@@ -0,0 +1,760 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>教学日程查询 - {{dateRange}}</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome -->
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- SheetJS for Excel export -->
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<!-- Day.js for date handling -->
<script src="https://cdn.jsdelivr.net/npm/dayjs@1.11.10/dayjs.min.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#f97316',
success: '#22c55e',
warning: '#eab308',
danger: '#ef4444',
info: '#fb923c',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.card-shadow {
box-shadow: 0 4px 24px rgba(249, 115, 22, 0.08);
}
.card-shadow-strong {
box-shadow: 0 8px 32px rgba(249, 115, 22, 0.12);
}
.card-hover {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 12px 40px rgba(249, 115, 22, 0.16);
}
.animate-fade-in {
animation: fadeIn 0.4s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.date-group-header {
position: sticky;
top: 90px;
z-index: 10;
backdrop-filter: blur(10px);
background-color: rgba(255, 247, 237, 0.85);
}
.glass-card {
background: linear-gradient(135deg, rgba(255,255,255,0.95) 0%, rgba(255,247,237,0.9) 100%);
border: 1px solid rgba(249, 115, 22, 0.08);
}
.stat-card {
background: linear-gradient(145deg, #ffffff 0%, #fff7ed 100%);
border: 1px solid rgba(249, 115, 22, 0.06);
}
.orange-glow {
box-shadow: 0 0 20px rgba(249, 115, 22, 0.15);
}
}
</style>
</head>
<body class="bg-orange-50/40 min-h-screen font-sans">
<!-- 头部导航 -->
<header class="bg-white/90 backdrop-blur-md shadow-sm sticky top-0 z-50 border-b border-orange-100">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div class="flex items-center gap-3">
<div class="w-10 h-10 bg-gradient-to-br from-orange-400 to-orange-600 rounded-xl flex items-center justify-center text-white shadow-lg shadow-orange-200">
<i class="fa fa-calendar-check-o text-lg"></i>
</div>
<h1 class="text-xl font-bold text-gray-800">教学日程查询</h1>
<span class="px-3 py-1 bg-orange-100 text-orange-600 rounded-full text-sm font-semibold border border-orange-200">
{{dateRange}}
</span>
</div>
<div class="flex flex-wrap items-center gap-3 w-full sm:w-auto">
<!-- 日期范围显示 -->
<div class="flex items-center gap-2 text-sm text-gray-600">
<span>共 {{totalDays}} 天</span>
</div>
<!-- 搜索框 -->
<div class="relative flex-1 sm:flex-initial min-w-[200px]">
<i class="fa fa-search absolute left-3 top-1/2 -translate-y-1/2 text-orange-300"></i>
<input
type="text"
id="searchInput"
placeholder="搜索班级、学生、日期..."
class="w-full pl-10 pr-4 py-2 border border-orange-200 rounded-xl focus:ring-2 focus:ring-orange-300/50 focus:border-orange-400 outline-none transition-all bg-white/80"
>
</div>
<!-- 导出按钮 -->
<button
id="exportBtn"
class="px-4 py-2 bg-gradient-to-r from-orange-400 to-orange-500 hover:from-orange-500 hover:to-orange-600 text-white rounded-xl flex items-center gap-2 transition-all shadow-lg shadow-orange-200 hover:shadow-xl hover:shadow-orange-300"
>
<i class="fa fa-download"></i>
<span>导出Excel</span>
</button>
</div>
</div>
</div>
</header>
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- 统计信息 -->
<div id="statsSection" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4 mb-8">
<div class="stat-card rounded-2xl p-6 card-shadow animate-fade-in">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500 mb-1">总天数</p>
<p id="totalDays" class="text-3xl font-bold text-gray-800">0</p>
</div>
<div class="w-12 h-12 bg-gradient-to-br from-orange-100 to-orange-200 rounded-2xl flex items-center justify-center text-orange-500">
<i class="fa fa-calendar text-xl"></i>
</div>
</div>
</div>
<div class="stat-card rounded-2xl p-6 card-shadow animate-fade-in" style="animation-delay: 0.1s">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500 mb-1">总课程</p>
<p id="totalClasses" class="text-3xl font-bold text-gray-800">0</p>
</div>
<div class="w-12 h-12 bg-gradient-to-br from-orange-100 to-amber-100 rounded-2xl flex items-center justify-center text-amber-500">
<i class="fa fa-book text-xl"></i>
</div>
</div>
</div>
<div class="stat-card rounded-2xl p-6 card-shadow animate-fade-in" style="animation-delay: 0.2s">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500 mb-1">学生人次</p>
<p id="totalStudents" class="text-3xl font-bold text-gray-800">0</p>
</div>
<div class="w-12 h-12 bg-gradient-to-br from-orange-100 to-rose-100 rounded-2xl flex items-center justify-center text-rose-400">
<i class="fa fa-users text-xl"></i>
</div>
</div>
</div>
<div class="stat-card rounded-2xl p-6 card-shadow animate-fade-in" style="animation-delay: 0.3s">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500 mb-1">出勤人次</p>
<p id="presentStudents" class="text-3xl font-bold text-green-500">0</p>
</div>
<div class="w-12 h-12 bg-gradient-to-br from-green-100 to-emerald-100 rounded-2xl flex items-center justify-center text-green-500">
<i class="fa fa-check-circle text-xl"></i>
</div>
</div>
</div>
<div class="stat-card rounded-2xl p-6 card-shadow animate-fade-in" style="animation-delay: 0.4s">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-500 mb-1">请假人次</p>
<p id="leaveStudents" class="text-3xl font-bold text-amber-500">0</p>
</div>
<div class="w-12 h-12 bg-gradient-to-br from-amber-100 to-yellow-100 rounded-2xl flex items-center justify-center text-amber-500">
<i class="fa fa-pause-circle text-xl"></i>
</div>
</div>
</div>
</div>
<!-- 筛选栏 -->
<div id="filterSection" class="glass-card rounded-2xl p-4 mb-6 card-shadow animate-fade-in" style="animation-delay: 0.5s">
<div class="flex flex-wrap items-center gap-4">
<span class="text-sm font-semibold text-gray-700">出勤筛选:</span>
<div class="flex flex-wrap gap-2">
<button class="filter-btn px-3 py-1.5 bg-gray-100 hover:bg-gray-200 rounded-full text-sm transition-all active" data-filter="all">
全部
</button>
<button class="filter-btn px-3 py-1.5 bg-green-50 hover:bg-green-100 text-green-600 rounded-full text-sm transition-all" data-filter="present">
✅ 出勤
</button>
<button class="filter-btn px-3 py-1.5 bg-amber-50 hover:bg-amber-100 text-amber-600 rounded-full text-sm transition-all" data-filter="leave">
⏸ 请假
</button>
<button class="filter-btn px-3 py-1.5 bg-red-50 hover:bg-red-100 text-red-500 rounded-full text-sm transition-all" data-filter="absent">
❌ 缺勤
</button>
</div>
<div class="flex items-center gap-2 ml-auto">
<span class="text-sm font-semibold text-gray-700">视图:</span>
<button id="viewByDate" class="px-3 py-1.5 bg-gradient-to-r from-orange-400 to-orange-500 text-white rounded-full text-sm transition-all shadow-md shadow-orange-200">
按日期
</button>
<button id="viewByClass" class="px-3 py-1.5 bg-gray-100 hover:bg-gray-200 rounded-full text-sm transition-all">
按班级
</button>
</div>
</div>
</div>
<!-- 课程列表 -->
<div id="classesContainer" class="space-y-8">
<!-- 课程会通过JavaScript动态生成 -->
<div id="emptyState" class="text-center py-16">
<div class="w-24 h-24 bg-gradient-to-br from-orange-100 to-orange-200 rounded-2xl flex items-center justify-center mx-auto mb-4 shadow-lg shadow-orange-100">
<i class="fa fa-calendar-o text-4xl text-orange-400"></i>
</div>
<h3 class="text-lg font-semibold text-gray-700 mb-2">暂无课程安排</h3>
<p class="text-gray-500">选择其他日期范围查看日程</p>
</div>
</div>
</main>
<!-- 页脚 -->
<footer class="bg-white/80 backdrop-blur-sm border-t border-orange-100 mt-16">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div class="text-center text-sm text-gray-500">
<p>© 2026 穹狼科创 · 教学日程查询系统</p>
<p class="mt-1">数据更新时间:{{updateTime}}</p>
</div>
</div>
</footer>
<script>
// 原始数据,会在生成网页时注入
// 格式:数组,每个元素是单天的日程数据 {teaching_date, items: [...]}
const scheduleData = {{scheduleData}};
// 当前配置
let currentFilter = 'all';
let searchKeyword = '';
let currentView = 'date'; // date 按日期, class 按班级
// 初始化页面
document.addEventListener('DOMContentLoaded', function() {
renderPage();
setupEventListeners();
});
// 设置事件监听器
function setupEventListeners() {
// 搜索框事件
document.getElementById('searchInput').addEventListener('input', function(e) {
searchKeyword = e.target.value.toLowerCase();
renderClasses();
});
// 筛选按钮事件
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active', 'bg-primary', 'text-white'));
this.classList.add('active', 'bg-primary', 'text-white');
currentFilter = this.dataset.filter;
renderClasses();
});
});
// 视图切换事件
document.getElementById('viewByDate').addEventListener('click', function() {
document.querySelectorAll('#filterSection button[id^="viewBy"]').forEach(b => b.classList.remove('bg-primary', 'text-white'));
document.querySelectorAll('#filterSection button[id^="viewBy"]').forEach(b => b.classList.add('bg-gray-100', 'hover:bg-gray-200'));
this.classList.remove('bg-gray-100', 'hover:bg-gray-200');
this.classList.add('bg-primary', 'text-white');
currentView = 'date';
renderClasses();
});
document.getElementById('viewByClass').addEventListener('click', function() {
document.querySelectorAll('#filterSection button[id^="viewBy"]').forEach(b => b.classList.remove('bg-primary', 'text-white'));
document.querySelectorAll('#filterSection button[id^="viewBy"]').forEach(b => b.classList.add('bg-gray-100', 'hover:bg-gray-200'));
this.classList.remove('bg-gray-100', 'hover:bg-gray-200');
this.classList.add('bg-primary', 'text-white');
currentView = 'class';
renderClasses();
});
// 导出按钮事件
document.getElementById('exportBtn').addEventListener('click', exportToExcel);
}
// 渲染整个页面
function renderPage() {
updateStats();
renderClasses();
}
// 更新统计信息
function updateStats() {
if (!scheduleData || scheduleData.length === 0) {
document.getElementById('totalDays').textContent = '0';
document.getElementById('totalClasses').textContent = '0';
document.getElementById('totalStudents').textContent = '0';
document.getElementById('presentStudents').textContent = '0';
document.getElementById('leaveStudents').textContent = '0';
return;
}
const totalDays = scheduleData.length;
let totalClasses = 0;
let totalStudents = 0;
let presentStudents = 0;
let leaveStudents = 0;
scheduleData.forEach(dayData => {
if (dayData.items) {
totalClasses += dayData.items.length;
dayData.items.forEach(cls => {
if (cls.students) {
totalStudents += cls.students.length;
cls.students.forEach(student => {
const status = student.attendance_status || '';
if (status.includes('出勤') || status === '✅ 出勤') {
presentStudents++;
} else if (status.includes('请假') || status === '⏸ 请假') {
leaveStudents++;
}
});
}
});
}
});
document.getElementById('totalDays').textContent = totalDays;
document.getElementById('totalClasses').textContent = totalClasses;
document.getElementById('totalStudents').textContent = totalStudents;
document.getElementById('presentStudents').textContent = presentStudents;
document.getElementById('leaveStudents').textContent = leaveStudents;
// 如果没有数据,显示空状态
if (totalClasses === 0) {
document.getElementById('emptyState').style.display = 'block';
document.getElementById('filterSection').style.display = 'none';
} else {
document.getElementById('emptyState').style.display = 'none';
document.getElementById('filterSection').style.display = 'block';
}
}
// 获取所有课程的打平数据
function getAllFlattenClasses() {
const allClasses = [];
scheduleData.forEach(dayData => {
if (dayData.items) {
dayData.items.forEach(cls => {
allClasses.push({
...cls,
teaching_date: dayData.teaching_date
});
});
}
});
return allClasses;
}
// 渲染课程列表
function renderClasses() {
const container = document.getElementById('classesContainer');
const allClasses = getAllFlattenClasses();
if (allClasses.length === 0) {
container.innerHTML = `
<div id="emptyState" class="text-center py-16">
<div class="w-24 h-24 bg-gradient-to-br from-orange-100 to-orange-200 rounded-2xl flex items-center justify-center mx-auto mb-4 shadow-lg shadow-orange-100">
<i class="fa fa-calendar-o text-4xl text-orange-400"></i>
</div>
<h3 class="text-lg font-semibold text-gray-700 mb-2">暂无课程安排</h3>
<p class="text-gray-500">选择其他日期范围查看日程</p>
</div>
`;
return;
}
// 过滤和搜索课程
const filteredClasses = allClasses.filter(cls => {
// 搜索匹配:日期、班级名称、学生姓名
const matchSearch =
searchKeyword === '' ||
cls.teaching_date.includes(searchKeyword) ||
cls.class_name.toLowerCase().includes(searchKeyword) ||
(cls.students && cls.students.some(s => s.student_name.toLowerCase().includes(searchKeyword)));
return matchSearch;
});
if (filteredClasses.length === 0) {
container.innerHTML = `
<div class="text-center py-16">
<div class="w-24 h-24 bg-gradient-to-br from-orange-100 to-orange-200 rounded-2xl flex items-center justify-center mx-auto mb-4 shadow-lg shadow-orange-100">
<i class="fa fa-search text-4xl text-orange-400"></i>
</div>
<h3 class="text-lg font-semibold text-gray-700 mb-2">没有找到匹配的结果</h3>
<p class="text-gray-500">请尝试其他搜索关键词</p>
</div>
`;
return;
}
let html = '';
if (currentView === 'date') {
// 按日期分组
const classesByDate = {};
filteredClasses.forEach(cls => {
if (!classesByDate[cls.teaching_date]) {
classesByDate[cls.teaching_date] = [];
}
classesByDate[cls.teaching_date].push(cls);
});
// 按日期排序
const sortedDates = Object.keys(classesByDate).sort();
sortedDates.forEach((date, dateIndex) => {
const dayClasses = classesByDate[date].sort((a, b) => a.start_time.localeCompare(b.start_time));
// 计算当天的统计
let dayTotalStudents = 0;
let dayPresent = 0;
let dayLeave = 0;
dayClasses.forEach(cls => {
if (cls.students) {
dayTotalStudents += cls.students.length;
cls.students.forEach(student => {
const status = student.attendance_status || '';
if (status.includes('出勤') || status === '✅ 出勤') dayPresent++;
else if (status.includes('请假') || status === '⏸ 请假') dayLeave++;
});
}
});
// 格式化日期,显示星期
const dateObj = new Date(date);
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
const weekDay = weekDays[dateObj.getDay()];
const formattedDate = `${date} (${weekDay})`;
html += `
<div class="date-group animate-fade-in" style="animation-delay: ${dateIndex * 0.1}s">
<!-- 日期分组头部 -->
<div class="date-group-header rounded-2xl p-4 mb-4 flex items-center justify-between border border-orange-100 shadow-sm">
<div class="flex items-center gap-3">
<h2 class="text-lg font-bold text-gray-800">${formattedDate}</h2>
<span class="px-2 py-1 bg-gradient-to-r from-orange-100 to-amber-100 text-orange-600 rounded-full text-xs font-semibold border border-orange-200">
${dayClasses.length} 节课
</span>
<span class="px-2 py-1 bg-gray-100 text-gray-600 rounded-full text-xs font-medium">
${dayTotalStudents} 人次
</span>
</div>
<div class="flex items-center gap-3 text-sm">
<div class="flex items-center gap-1">
<span class="w-2 h-2 bg-green-400 rounded-full"></span>
<span class="text-green-600 font-medium">${dayPresent}</span>
</div>
<div class="flex items-center gap-1">
<span class="w-2 h-2 bg-amber-400 rounded-full"></span>
<span class="text-amber-600 font-medium">${dayLeave}</span>
</div>
<div class="flex items-center gap-1">
<span class="w-2 h-2 bg-gray-300 rounded-full"></span>
<span class="text-gray-500">${dayTotalStudents - dayPresent - dayLeave}</span>
</div>
</div>
</div>
<!-- 当天的课程列表 -->
<div class="space-y-4 pl-2 border-l-2 border-orange-200">
`;
// 渲染当天的课程
dayClasses.forEach((cls, classIndex) => {
html += renderClassCard(cls, classIndex);
});
html += `
</div>
</div>
`;
});
} else {
// 按班级分组
const classesByClassName = {};
filteredClasses.forEach(cls => {
if (!classesByClassName[cls.class_name]) {
classesByClassName[cls.class_name] = [];
}
classesByClassName[cls.class_name].push(cls);
});
// 按班级名称排序
const sortedClassNames = Object.keys(classesByClassName).sort();
sortedClassNames.forEach((className, classIndex) => {
const classSessions = classesByClassName[className].sort((a, b) => a.teaching_date.localeCompare(b.teaching_date));
html += `
<div class="class-group animate-fade-in" style="animation-delay: ${classIndex * 0.1}s">
<!-- 班级分组头部 -->
<div class="date-group-header rounded-2xl p-4 mb-4 flex items-center justify-between border border-orange-100 shadow-sm">
<div class="flex items-center gap-3">
<h2 class="text-lg font-bold text-gray-800">${className}</h2>
<span class="px-2 py-1 bg-gradient-to-r from-orange-100 to-amber-100 text-orange-600 rounded-full text-xs font-semibold border border-orange-200">
${classSessions.length} 次课
</span>
</div>
<div class="text-sm text-gray-600">
${classSessions[0].teaching_date}${classSessions[classSessions.length - 1].teaching_date}
</div>
</div>
<!-- 该班级的所有课程 -->
<div class="space-y-4 pl-2 border-l-2 border-orange-200">
`;
// 渲染该班级的每一次课
classSessions.forEach((cls, sessionIndex) => {
html += renderClassCard(cls, sessionIndex, true);
});
html += `
</div>
</div>
`;
});
}
container.innerHTML = html;
// 添加卡片展开/收起事件
document.querySelectorAll('.class-header').forEach(header => {
header.addEventListener('click', function() {
const content = this.nextElementSibling;
const icon = this.querySelector('.expand-icon');
if (content.classList.contains('hidden')) {
content.classList.remove('hidden');
icon.classList.add('rotate-180');
} else {
content.classList.add('hidden');
icon.classList.remove('rotate-180');
}
});
});
}
// 渲染单个课程卡片
function renderClassCard(cls, index, showDate = false) {
// 计算班级出勤统计
let presentCount = 0;
let leaveCount = 0;
let absentCount = 0;
let totalCount = cls.students ? cls.students.length : 0;
if (cls.students) {
cls.students.forEach(student => {
const status = student.attendance_status || '';
if (status.includes('出勤') || status === '✅ 出勤') presentCount++;
else if (status.includes('请假') || status === '⏸ 请假') leaveCount++;
else if (status.includes('缺勤') || status === '❌ 缺勤') absentCount++;
});
}
// 过滤学生列表
let filteredStudents = cls.students || [];
if (currentFilter !== 'all') {
filteredStudents = filteredStudents.filter(student => {
const status = student.attendance_status || '';
if (currentFilter === 'present') return status.includes('出勤') || status === '✅ 出勤';
if (currentFilter === 'leave') return status.includes('请假') || status === '⏸ 请假';
if (currentFilter === 'absent') return status.includes('缺勤') || status === '❌ 缺勤';
return true;
});
}
// 搜索学生
if (searchKeyword) {
filteredStudents = filteredStudents.filter(s =>
s.student_name.toLowerCase().includes(searchKeyword)
);
}
// 生成学生表格HTML
const studentsTableHTML = filteredStudents.length > 0 ? `
<div class="overflow-x-auto mt-4">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-orange-100">
<th class="text-left py-3 px-4 font-semibold text-gray-600">序号</th>
<th class="text-left py-3 px-4 font-semibold text-gray-600">学生姓名</th>
<th class="text-left py-3 px-4 font-semibold text-gray-600">出勤状态</th>
<th class="text-left py-3 px-4 font-semibold text-gray-600">备注</th>
</tr>
</thead>
<tbody>
${filteredStudents.map((student, sIndex) => {
let statusClass = '';
let statusText = student.attendance_status || '✅ 出勤';
if (statusText.includes('出勤') || statusText === '✅ 出勤') {
statusClass = 'text-green-600 bg-green-50 border border-green-100';
} else if (statusText.includes('请假') || statusText === '⏸ 请假') {
statusClass = 'text-amber-600 bg-amber-50 border border-amber-100';
} else if (statusText.includes('缺勤') || statusText === '❌ 缺勤') {
statusClass = 'text-red-500 bg-red-50 border border-red-100';
}
return `
<tr class="border-b border-orange-50 hover:bg-orange-50/30 transition-colors">
<td class="py-3 px-4 text-gray-500">${sIndex + 1}</td>
<td class="py-3 px-4 font-semibold text-gray-800">${student.student_name || '-'}</td>
<td class="py-3 px-4">
<span class="px-2.5 py-1 rounded-full text-xs font-semibold ${statusClass}">
${statusText}
</span>
</td>
<td class="py-3 px-4 text-gray-500">${student.remark || '-'}</td>
</tr>
`;
}).join('')}
</tbody>
</table>
</div>
` : `
<div class="text-center py-8 text-gray-500">
<i class="fa fa-info-circle mr-2 text-orange-400"></i>没有匹配的学生数据
</div>
`;
return `
<div class="glass-card rounded-2xl overflow-hidden card-shadow card-hover">
<!-- 课程卡片头部 -->
<div class="p-6 cursor-pointer class-header" data-class-id="${cls.class_id || index}">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div class="flex-1">
<div class="flex items-center gap-3 mb-2">
<h3 class="text-lg font-bold text-gray-800">${cls.class_name || '未命名班级'}</h3>
${showDate ? `
<span class="px-2 py-1 bg-gradient-to-r from-blue-50 to-indigo-50 text-blue-600 rounded-full text-xs font-semibold border border-blue-100">
${cls.teaching_date}
</span>
` : ''}
<span class="px-2.5 py-1 bg-gradient-to-r from-orange-100 to-amber-100 text-orange-600 rounded-full text-xs font-semibold border border-orange-200">
${totalCount}
</span>
</div>
<div class="flex flex-wrap items-center gap-4 text-sm text-gray-600">
<div class="flex items-center gap-1.5">
<i class="fa fa-clock-o text-orange-300"></i>
<span>${cls.start_time || '-'} - ${cls.end_time || '-'}</span>
</div>
<div class="flex items-center gap-1.5">
<i class="fa fa-user text-orange-300"></i>
<span>授课老师:${cls.teacher_name || '-'}</span>
</div>
<div class="flex items-center gap-1.5">
<i class="fa fa-map-marker text-orange-300"></i>
<span>教室:${cls.classroom || '-'}</span>
</div>
</div>
</div>
<div class="flex items-center gap-4">
<!-- 出勤统计 -->
<div class="flex items-center gap-3 bg-gray-50 rounded-xl px-3 py-2">
<div class="flex items-center gap-1 text-sm">
<span class="w-2 h-2 bg-green-400 rounded-full"></span>
<span class="text-green-600 font-semibold">${presentCount}</span>
</div>
<div class="flex items-center gap-1 text-sm">
<span class="w-2 h-2 bg-amber-400 rounded-full"></span>
<span class="text-amber-600 font-semibold">${leaveCount}</span>
</div>
<div class="flex items-center gap-1 text-sm">
<span class="w-2 h-2 bg-red-400 rounded-full"></span>
<span class="text-red-500 font-semibold">${absentCount}</span>
</div>
</div>
<!-- 展开/收起图标 -->
<div class="w-8 h-8 bg-orange-50 rounded-full flex items-center justify-center">
<i class="fa fa-chevron-down text-orange-400 transition-transform transform rotate-0 expand-icon"></i>
</div>
</div>
</div>
</div>
<!-- 学生列表内容(默认隐藏) -->
<div class="class-content border-t border-orange-100 px-6 pb-6 hidden">
${studentsTableHTML}
</div>
</div>
`;
}
// 导出到Excel
function exportToExcel() {
const allClasses = getAllFlattenClasses();
if (allClasses.length === 0) {
alert('没有数据可以导出');
return;
}
// 准备导出数据
const exportData = [];
allClasses.forEach(cls => {
if (cls.students) {
cls.students.forEach(student => {
exportData.push({
'日期': cls.teaching_date,
'班级名称': cls.class_name || '-',
'上课时间': `${cls.start_time || '-'} - ${cls.end_time || '-'}`,
'授课老师': cls.teacher_name || '-',
'教室': cls.classroom || '-',
'学生姓名': student.student_name || '-',
'出勤状态': student.attendance_status || '✅ 出勤',
'备注': student.remark || '-'
});
});
}
});
if (exportData.length === 0) {
alert('没有学生数据可以导出');
return;
}
// 创建工作簿
const ws = XLSX.utils.json_to_sheet(exportData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "教学日程");
// 设置列宽
const wscols = [
{wch: 12}, // 日期
{wch: 20}, // 班级名称
{wch: 18}, // 上课时间
{wch: 12}, // 授课老师
{wch: 10}, // 教室
{wch: 12}, // 学生姓名
{wch: 10}, // 出勤状态
{wch: 20}, // 备注
];
ws['!cols'] = wscols;
// 导出文件
const dateRange = document.title.replace('教学日程查询 - ', '');
XLSX.writeFile(wb, `教学日程_${dateRange}.xlsx`);
}
</script>
</body>
</html>