feat: 多班级版班级管理系统 v2.0

技术栈:Go (Gin + GORM) + PHP + MySQL 5.7 + Redis

主要功能:
- 多班级完全隔离(class_id 贯穿全系统)
- 后端 Go Gin(端口 56789),Nginx 反代
- 超级管理员独立登录(env 配置,默认账密 admin/Admin123)
- bcrypt 密码加密(无 PASSWORD_SALT)
- 科任老师/课代表新角色
- 课代表作业管理页面
- 排行榜分项排行(操行分/考勤/作业)
- 角色加减分上下限由班主任配置
- 家长改密功能(可开关)
- 班级角色按需开关
- 宿舍号格式:南0-000
- 周度/月度重置功能
- MySQL 5.7 兼容
- 43 轮代码审查 + 全部修复

开发者: Canglan
版权归属: Sea Network Technology Studio
许可证: Apache License 2.0
This commit is contained in:
2026-06-22 10:21:52 +08:00
commit 4a82eff3c6
135 changed files with 19963 additions and 0 deletions

214
frontend/admin/classes.php Normal file
View File

@@ -0,0 +1,214 @@
<?php
/**
* 共享班级管理系统 - 班级管理页面
*
* 开发者: Canglan
* 联系方式: admin@sea-studio.top
* 版权归属: Sea Network Technology Studio
* 许可证: Apache License 2.0
*
* 版权所有 © Sea Network Technology Studio
*/
$page_title = '班级管理';
require_once __DIR__ . '/../config.php';
// 权限检查
if (!isset($_SESSION['user_type']) || $_SESSION['user_type'] !== 'super_admin') {
header('Location: /admin/dashboard.php');
exit();
}
require_once __DIR__ . '/../includes/header.php';
require_once __DIR__ . '/../includes/nav.php';
?>
<div class="container">
<div class="page-header">
<h2>班级管理</h2>
<button class="btn btn-primary" onclick="showCreateModal()">新增班级</button>
</div>
<div id="classList" class="card-grid">
<div class="loading">加载中...</div>
</div>
</div>
<!-- 创建/编辑班级弹窗 -->
<div id="classModal" class="modal" style="display:none;">
<div class="modal-content">
<div class="modal-header">
<h3 id="modalTitle">新增班级</h3>
<button class="btn-close" onclick="closeModal()">&times;</button>
</div>
<div class="modal-body">
<input type="hidden" id="editClassId">
<div class="form-group">
<label>班级名称 <span class="required">*</span></label>
<input type="text" id="className" placeholder="如:高一(1)班" maxlength="100">
</div>
<div class="form-group">
<label>年级</label>
<input type="text" id="classGrade" placeholder="如:高一" maxlength="50">
</div>
<div class="form-group">
<label>描述</label>
<textarea id="classDesc" placeholder="班级描述(选填)" maxlength="255"></textarea>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal()">取消</button>
<button class="btn btn-primary" onclick="saveClass()">保存</button>
</div>
</div>
</div>
<script>
async function loadClasses() {
const result = await apiGet('/api/class/list', { include_disabled: true });
if (result && result.success) {
renderClasses(result.data.classes || []);
} else {
document.getElementById('classList').innerHTML = '<div class="empty-state">暂无班级数据</div>';
}
}
function renderClasses(classes) {
if (classes.length === 0) {
document.getElementById('classList').innerHTML = '<div class="empty-state">暂无班级数据,点击右上角"新增班级"创建</div>';
return;
}
let html = '';
classes.forEach(cls => {
const statusBadge = cls.status === 1
? '<span class="status-badge status-submitted">启用</span>'
: '<span class="status-badge status-not_submitted">禁用</span>';
html += `
<div class="card">
<div class="card-header">
<h3>${escapeHtml(cls.class_name)}</h3>
${statusBadge}
</div>
<div class="card-body">
<p>年级: ${escapeHtml(cls.grade || '-')}</p>
<p>学生人数: ${cls.student_count || 0}</p>
<p>描述: ${escapeHtml(cls.description || '-')}</p>
</div>
<div class="card-footer">
<button class="btn btn-sm btn-primary" onclick="switchClass(${cls.class_id}, '${escapeHtml(cls.class_name)}')">进入班级</button>
<button class="btn btn-sm btn-secondary" onclick="editClass(${cls.class_id}, '${escapeHtml(cls.class_name)}', '${escapeHtml(cls.grade || '')}', '${escapeHtml(cls.description || '')}')">编辑</button>
<button class="btn btn-sm btn-danger" onclick="deleteClass(${cls.class_id}, '${escapeHtml(cls.class_name)}')">删除</button>
</div>
</div>
`;
});
document.getElementById('classList').innerHTML = html;
}
function showCreateModal() {
document.getElementById('modalTitle').textContent = '新增班级';
document.getElementById('editClassId').value = '';
document.getElementById('className').value = '';
document.getElementById('classGrade').value = '';
document.getElementById('classDesc').value = '';
document.getElementById('classModal').style.display = 'flex';
}
function editClass(classId, name, grade, desc) {
document.getElementById('modalTitle').textContent = '编辑班级';
document.getElementById('editClassId').value = classId;
document.getElementById('className').value = name;
document.getElementById('classGrade').value = grade;
document.getElementById('classDesc').value = desc;
document.getElementById('classModal').style.display = 'flex';
}
function closeModal() {
document.getElementById('classModal').style.display = 'none';
}
async function saveClass() {
const classId = document.getElementById('editClassId').value;
const data = {
class_name: document.getElementById('className').value.trim(),
grade: document.getElementById('classGrade').value.trim() || null,
description: document.getElementById('classDesc').value.trim() || null
};
if (!data.class_name) {
showToast('请输入班级名称', 'error');
return;
}
let result;
if (classId) {
result = await apiPut(`/api/class/update/${classId}`, data);
} else {
result = await apiPost('/api/class/create', data);
}
if (result && result.success) {
showToast(classId ? '班级更新成功' : '班级创建成功');
closeModal();
loadClasses();
} else {
showToast(result ? result.message : '操作失败', 'error');
}
}
async function deleteClass(classId, className) {
if (!confirm(`确定要删除班级 "${className}" 吗?此操作不可撤销。`)) return;
const result = await apiDelete(`/api/class/delete/${classId}`);
if (result && result.success) {
showToast('班级已删除');
loadClasses();
} else {
showToast(result ? result.message : '删除失败', 'error');
}
}
async function switchClass(classId, className) {
const result = await apiPost('/api/class/switch', { class_id: classId });
if (result && result.success) {
// 更新本地存储的用户信息
const userInfo = getUserInfo();
if (userInfo) {
userInfo.class_id = classId;
userInfo.class_name = className;
// 更新token
if (result.data && result.data.token) {
localStorage.setItem(window.JWT_STORAGE_KEY || 'class_system_token', result.data.token);
}
setUserInfo(userInfo);
}
// 同步到 PHP Session
try {
await fetch('/api/save_session.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + getToken() },
body: JSON.stringify({
user_id: userInfo.user_id,
user_type: userInfo.user_type,
username: userInfo.username,
real_name: userInfo.real_name,
role: userInfo.role,
class_id: classId,
class_name: className
})
});
} catch (e) {
console.warn('同步Session失败', e);
}
showToast(`已切换到: ${className}`);
window.location.href = '/admin/dashboard.php';
} else {
showToast(result ? result.message : '切换失败', 'error');
}
}
document.addEventListener('DOMContentLoaded', loadClasses);
</script>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>