v1.2版本更新发布

This commit is contained in:
2026-04-22 00:59:29 +08:00
parent 194c076456
commit 4121e9624f
26 changed files with 1323 additions and 61 deletions

View File

@@ -0,0 +1,307 @@
<?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 class="form-group">
<label>开始日期</label>
<input type="date" id="semesterStartDate">
</div>
<div class="form-group">
<label>结束日期</label>
<input type="date" id="semesterEndDate">
</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>排名</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;
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('学期创建成功并已激活');
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>
</tr>`;
});
if (archives.length === 0) {
html = '<tr><td colspan="4" 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'; ?>