Files
ClassManager/frontend/admin/semesters.php
2026-04-22 02:37:27 +08:00

353 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 班级操行分管理系统 - 学期管理页面
*
* 开发者: Canglan
* 联系方式: admin@sea-studio.top
* 版权归属: Sea Network Technology Studio
* 许可证: MIT License
*
* 版权所有 © Sea Network Technology Studio
*/
require_once __DIR__ . '/../config.php';
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'admin') {
header('Location: /index.php');
exit();
}
$role = $_SESSION['role'] ?? '';
if ($role !== '班主任') {
header('Location: /admin/dashboard.php');
exit();
}
$page_title = '学期管理';
include __DIR__ . '/../includes/header.php';
?>
<?php include __DIR__ . '/../includes/nav.php'; ?>
<div class="container">
<div class="card">
<div class="action-bar">
<button class="btn btn-primary" onclick="showCreateSemesterModal()">创建新学期</button>
</div>
<div class="table-wrapper">
<table class="table">
<thead>
<tr>
<th>学期名称</th>
<th>开始日期</th>
<th>结束日期</th>
<th>状态</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="semesterList"></tbody>
</table>
</div>
</div>
</div>
<!-- 创建学期模态框 -->
<div id="createSemesterModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>创建新学期</h3>
<button class="modal-close" onclick="closeModal('createSemesterModal')">&times;</button>
</div>
<form onsubmit="event.preventDefault(); submitCreateSemester()">
<div class="form-group">
<label>学期名称 <span style="color:red;">*</span></label>
<input type="text" id="semesterName" required placeholder="如2025春季学期" maxlength="100">
</div>
<div style="margin-bottom: 8px;">
<button type="button" class="btn btn-sm" style="border: 1px solid #667eea; color: #667eea; margin-right: 6px;" onclick="fillSemesterDates('upper')">上学期9月-次年2月</button>
<button type="button" class="btn btn-sm" style="border: 1px solid #667eea; color: #667eea;" onclick="fillSemesterDates('lower')">下学期3月-7月</button>
</div>
<div class="form-group">
<label>开始日期</label>
<input type="date" id="semesterStartDate">
</div>
<div class="form-group">
<label>结束日期 <small style="color: #999;">(可选)</small></label>
<input type="date" id="semesterEndDate" placeholder="可选,不确定可不填">
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">创建学期</button>
<button type="button" class="btn" onclick="closeModal('createSemesterModal')">取消</button>
</div>
</form>
</div>
</div>
<!-- 归档确认模态框 -->
<div id="archiveConfirmModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>确认归档学期</h3>
<button class="modal-close" onclick="closeModal('archiveConfirmModal')">&times;</button>
</div>
<div class="form-group">
<p id="archiveConfirmText" style="margin: 10px 0;"></p>
<p style="color: #e74c3c; font-size: 14px;">注意:归档前需确保学期已设置开始日期,否则无法归档。归档后该学期的操行分记录将不可修改或撤销,但可以查看归档数据。</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" onclick="confirmArchive()">确认归档</button>
<button type="button" class="btn" onclick="closeModal('archiveConfirmModal')">取消</button>
</div>
</div>
</div>
<!-- 归档数据查看模态框 -->
<div id="archiveDataModal" class="modal">
<div class="modal-content" style="max-width: 700px;">
<div class="modal-header">
<h3 id="archiveDataTitle">归档数据</h3>
<button class="modal-close" onclick="closeModal('archiveDataModal')">&times;</button>
</div>
<div class="table-wrapper">
<table class="table">
<thead>
<tr>
<th colspan="2" style="text-align:center; border-bottom: none;">基本信息</th>
<th rowspan="2" style="vertical-align: middle;">姓名</th>
<th rowspan="2" style="vertical-align: middle;">操行分</th>
<th colspan="4" style="text-align:center; border-bottom: none;">考勤统计</th>
<th colspan="3" style="text-align:center; border-bottom: none;">作业统计</th>
</tr>
<tr>
<th>排名</th>
<th>学号</th>
<th>出勤</th>
<th>缺勤</th>
<th>迟到</th>
<th>请假</th>
<th>已交</th>
<th>未交</th>
<th>迟交</th>
</tr>
</thead>
<tbody id="archiveDataList"></tbody>
</table>
</div>
<div class="pagination" id="archivePagination"></div>
<div class="modal-footer">
<button type="button" class="btn" onclick="closeModal('archiveDataModal')">关闭</button>
</div>
</div>
</div>
<script>
var archiveSemesterId = null;
var archivePage = 1;
var archiveTotalPages = 1;
function fillSemesterDates(type) {
var now = new Date();
var currentYear = now.getFullYear();
var currentMonth = now.getMonth() + 1;
var startDateInput = document.getElementById('semesterStartDate');
var endDateInput = document.getElementById('semesterEndDate');
if (type === 'upper') {
var year = currentMonth >= 6 ? currentYear : currentYear - 1;
var endYear = year + 1;
var febDay = 28;
if ((endYear % 4 === 0 && endYear % 100 !== 0) || endYear % 400 === 0) {
febDay = 29;
}
startDateInput.value = year + '-09-01';
endDateInput.value = endYear + '-02-' + febDay;
} else if (type === 'lower') {
startDateInput.value = currentYear + '-03-01';
endDateInput.value = currentYear + '-07-15';
}
}
async function loadSemesters() {
const res = await apiGet('/api/semester/list');
if (res && res.success) {
let html = '';
const semesters = res.data || [];
semesters.forEach(sem => {
let statusText = '';
let statusClass = '';
if (sem.is_archived) {
statusText = '已归档';
statusClass = 'status-badge status-not_submitted';
} else if (sem.is_active) {
statusText = '当前学期';
statusClass = 'status-badge status-submitted';
} else {
statusText = '未激活';
statusClass = 'status-badge status-late';
}
let actions = '';
if (!sem.is_archived) {
if (!sem.is_active) {
actions += `<button class="btn btn-sm btn-primary" onclick="activateSemester(${sem.semester_id})">激活</button> `;
}
actions += `<button class="btn btn-sm btn-warning" onclick="showArchiveConfirm(${sem.semester_id}, '${escapeHtml(sem.semester_name)}')">归档</button> `;
}
if (sem.is_archived) {
actions += `<button class="btn btn-sm btn-secondary" onclick="viewArchiveData(${sem.semester_id}, '${escapeHtml(sem.semester_name)}')">查看归档</button>`;
}
html += `<tr>
<td>${escapeHtml(sem.semester_name)}</td>
<td>${formatDate(sem.start_date)}</td>
<td>${formatDate(sem.end_date)}</td>
<td><span class="${statusClass}">${statusText}</span></td>
<td>${formatDateTime(sem.created_at)}</td>
<td>${actions}</td>
</tr>`;
});
if (semesters.length === 0) {
html = '<tr><td colspan="6" style="text-align:center;">暂无学期,请点击上方按钮创建新学期</td></tr>';
}
document.getElementById('semesterList').innerHTML = html;
}
}
function showCreateSemesterModal() {
document.getElementById('semesterName').value = '';
document.getElementById('semesterStartDate').value = '';
document.getElementById('semesterEndDate').value = '';
document.getElementById('createSemesterModal').style.display = 'flex';
}
async function submitCreateSemester() {
const name = document.getElementById('semesterName').value.trim();
const startDate = document.getElementById('semesterStartDate').value;
const endDate = document.getElementById('semesterEndDate').value;
if (!name) {
showToast('请输入学期名称', 'warning');
return;
}
const res = await apiPost('/api/semester/create', {
semester_name: name,
start_date: startDate || null,
end_date: endDate || null
});
if (res && res.success) {
showToast(res.message || '学期创建成功');
closeModal('createSemesterModal');
loadSemesters();
} else {
showToast(res?.message || '创建失败', 'error');
}
}
async function activateSemester(semesterId) {
if (!confirm('确认将此学期设为当前活跃学期?其他学期将被设为非活跃。')) {
return;
}
const res = await apiPut(`/api/semester/activate/${semesterId}`);
if (res && res.success) {
showToast(res.message || '已设为当前学期');
loadSemesters();
} else {
showToast(res?.message || '操作失败', 'error');
}
}
function showArchiveConfirm(semesterId, semesterName) {
archiveSemesterId = semesterId;
document.getElementById('archiveConfirmText').innerHTML =
`确定要归档学期 "<strong>${semesterName}</strong>" 吗?<br>归档后将保存所有学生的当前操行分快照,该学期数据将变为只读。`;
document.getElementById('archiveConfirmModal').style.display = 'flex';
}
async function confirmArchive() {
if (!archiveSemesterId) return;
const res = await apiPost(`/api/semester/archive/${archiveSemesterId}`);
if (res && res.success) {
showToast(res.message || '归档成功');
closeModal('archiveConfirmModal');
archiveSemesterId = null;
loadSemesters();
} else {
showToast(res?.message || '归档失败', 'error');
}
}
async function viewArchiveData(semesterId, semesterName, page) {
page = page || 1;
archivePage = page;
document.getElementById('archiveDataTitle').textContent = `归档数据 - ${semesterName}`;
const res = await apiGet(`/api/semester/archive/${semesterId}/records`, {
page: page, page_size: 50
});
if (res && res.success) {
const data = res.data || {};
const archives = data.archives || [];
let html = '';
archives.forEach(a => {
html += `<tr>
<td>${a.rank_position || '-'}</td>
<td>${escapeHtml(a.student_no)}</td>
<td>${escapeHtml(a.student_name)}</td>
<td>${a.final_points}</td>
<td>${a.attendance_present || 0}</td>
<td>${a.attendance_absent || 0}</td>
<td>${a.attendance_late || 0}</td>
<td>${a.attendance_leave || 0}</td>
<td>${a.homework_submitted || 0}</td>
<td>${a.homework_not_submitted || 0}</td>
<td>${a.homework_late || 0}</td>
</tr>`;
});
if (archives.length === 0) {
html = '<tr><td colspan="11" style="text-align:center;">暂无归档数据</td></tr>';
}
document.getElementById('archiveDataList').innerHTML = html;
archiveTotalPages = data.total_pages || 1;
renderArchivePagination(semesterId, semesterName);
document.getElementById('archiveDataModal').style.display = 'flex';
} else {
showToast(res?.message || '获取归档数据失败', 'error');
}
}
function renderArchivePagination(semesterId, semesterName) {
const container = document.getElementById('archivePagination');
if (!container) return;
if (archiveTotalPages <= 1) {
container.innerHTML = '';
return;
}
let html = '';
for (let i = 1; i <= archiveTotalPages; i++) {
if (i === archivePage) {
html += `<span class="active">${i}</span>`;
} else {
html += `<a href="#" onclick="viewArchiveData(${semesterId}, '${escapeHtml(semesterName)}', ${i}); return false;">${i}</a>`;
}
}
container.innerHTML = html;
}
function closeModal(modalId) {
const modal = document.getElementById(modalId);
if (modal) modal.style.display = 'none';
}
loadSemesters();
</script>
<script src="/assets/js/admin.js"></script>
<?php include __DIR__ . '/../includes/footer.php'; ?>