Files
SharedClassManager/frontend/admin/class_settings.php
canglan c6db68a9f4 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
2026-06-23 16:02:28 +08:00

440 lines
18 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
* 许可证: Apache License 2.0
*
* 版权所有 © Sea Network Technology Studio
*/
$page_title = '班级设置';
require_once __DIR__ . '/../config.php';
if (!isset($_SESSION['user_id'])) {
header('Location: /index.php');
exit();
}
$role = $_SESSION['role'] ?? '';
if (!in_array($role, ['班主任', '系统管理员'])) {
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>
<p class="text-muted">修改当前班级的扣分规则、加减分限制和功能开关(仅班主任可修改)</p>
</div>
<!-- 扣分规则 -->
<div class="card">
<h3>扣分规则</h3>
<div id="deductionRules">
<div class="form-group">
<label>学生初始操行分</label>
<input type="number" id="setting_student_initial_points" value="60" min="0" max="200">
</div>
<div class="form-group">
<label>作业未提交扣分</label>
<input type="number" id="setting_deduction_homework_not_submit" value="2" min="0" max="20">
</div>
<div class="form-group">
<label>作业迟交扣分</label>
<input type="number" id="setting_deduction_homework_late" value="1" min="0" max="20">
</div>
<div class="form-group">
<label>缺勤扣分</label>
<input type="number" id="setting_deduction_attendance_absent" value="3" min="0" max="20">
</div>
<div class="form-group">
<label>迟到扣分</label>
<input type="number" id="setting_deduction_attendance_late" value="1" min="0" max="20">
</div>
<div class="form-group">
<label>请假扣分0=不扣分)</label>
<input type="number" id="setting_deduction_attendance_leave" value="0" min="0" max="20">
</div>
<button class="btn btn-primary" onclick="saveSettings()">保存设置</button>
</div>
</div>
<!-- 角色加减分上下限 -->
<div class="card">
<h3>角色加减分限制</h3>
<p class="text-muted" style="margin-bottom:15px;">配置各角色单次加减分的上下限</p>
<div id="pointLimits">
<div class="settings-grid">
<div class="form-group">
<label>班长单次加分上限</label>
<input type="number" id="limit_monitor_max_add" value="5" min="0" max="100">
</div>
<div class="form-group">
<label>班长单次扣分下限</label>
<input type="number" id="limit_monitor_max_subtract" value="-5" min="-100" max="0">
</div>
<div class="form-group">
<label>学习委员加分上限</label>
<input type="number" id="limit_study_comm_max_points" value="5" min="0" max="100">
</div>
<div class="form-group">
<label>学习委员扣分下限</label>
<input type="number" id="limit_study_comm_min_points" value="-5" min="-100" max="0">
</div>
<div class="form-group">
<label>考勤委员扣分上限</label>
<input type="number" id="limit_attendance_rep_max_points" value="8" min="0" max="100">
</div>
<div class="form-group">
<label>考勤委员扣分下限</label>
<input type="number" id="limit_attendance_rep_min_points" value="-8" min="-100" max="0">
</div>
<div class="form-group">
<label>劳动委员加分上限</label>
<input type="number" id="limit_labor_rep_max_points" value="1" min="0" max="100">
</div>
<div class="form-group">
<label>劳动委员扣分下限</label>
<input type="number" id="limit_labor_rep_min_points" value="-1" min="-100" max="0">
</div>
<div class="form-group">
<label>志愿委员加分上限</label>
<input type="number" id="limit_volunteer_rep_max_points" value="5" min="0" max="100">
</div>
<div class="form-group">
<label>志愿委员扣分下限</label>
<input type="number" id="limit_volunteer_rep_min_points" value="-5" min="-100" max="0">
</div>
<div class="form-group">
<label>科任老师加分上限</label>
<input type="number" id="limit_subject_teacher_max_points" value="5" min="0" max="100">
</div>
<div class="form-group">
<label>科任老师扣分下限</label>
<input type="number" id="limit_subject_teacher_min_points" value="-5" min="-100" max="0">
</div>
<button class="btn btn-primary" onclick="savePointLimits()">保存加减分限制</button>
</div>
</div>
<!-- 周期重置 -->
<div class="card">
<h3>周期重置</h3>
<p class="text-muted" style="margin-bottom:15px;">按周或按月自动重置学生操行分(需配合定时任务或手动触发)</p>
<div id="periodResetSettings">
<div class="form-group">
<label>重置频率</label>
<select id="setting_reset_frequency" onchange="toggleResetDay()">
<option value="none">不重置(仅学期结算)</option>
<option value="weekly">每周重置</option>
<option value="monthly">每月重置</option>
</select>
</div>
<div class="form-group" id="reset_day_of_week_group" style="display:none;">
<label>每周重置日</label>
<select id="setting_reset_day_of_week">
<option value="1">周一</option>
<option value="2">周二</option>
<option value="3">周三</option>
<option value="4">周四</option>
<option value="5">周五</option>
<option value="6">周六</option>
<option value="7">周日</option>
</select>
</div>
<div class="form-group" id="reset_day_of_month_group" style="display:none;">
<label>每月重置日</label>
<input type="number" id="setting_reset_day_of_month" value="1" min="1" max="28">
<small style="color:#999;">1~28日建议避免月末最后几天</small>
</div>
<button class="btn btn-primary" onclick="savePeriodResetSettings()">保存周期重置设置</button>
</div>
</div>
<!-- 角色开关 -->
<div class="card">
<h3>功能开关</h3>
<p class="text-muted" style="margin-bottom:15px;">控制各角色的功能启用状态</p>
<div id="roleToggles">
<div class="toggle-group">
<label class="toggle-label">
<input type="checkbox" id="toggle_parent_account_enabled">
<span>家长账号启用</span>
</label>
<label class="toggle-label">
<input type="checkbox" id="toggle_parent_password_change_enabled">
<span>家长改密启用</span>
</label>
<label class="toggle-label">
<input type="checkbox" id="toggle_parent_view_attendance">
<span>家长查看考勤</span>
</label>
<label class="toggle-label">
<input type="checkbox" id="toggle_parent_view_ranking">
<span>家长查看排名</span>
</label>
<label class="toggle-label">
<input type="checkbox" id="toggle_student_view_ranking">
<span>学生查看排行榜</span>
</label>
<label class="toggle-label">
<input type="checkbox" id="toggle_homework_management">
<span>作业管理模块</span>
</label>
<label class="toggle-label">
<input type="checkbox" id="toggle_attendance_management">
<span>考勤管理模块</span>
</label>
<label class="toggle-label">
<input type="checkbox" id="toggle_cadre_homework">
<span>课代表作业管理</span>
</label>
</div>
<button class="btn btn-primary" onclick="saveRoleToggles()">保存功能开关</button>
</div>
</div>
</div>
<style>
.settings-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 15px;
margin-bottom: 15px;
}
.toggle-group {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 15px;
}
.toggle-label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
font-size: 14px;
}
.toggle-label input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
background: #fff;
cursor: pointer;
}
</style>
<script>
// limitFieldMap: 前端 input ID 后缀 → 后端 class_settings 表 key
var limitFieldMap = {
'monitor_max_add': 'point_limit_班长_max',
'monitor_max_subtract': 'point_limit_班长_min',
'study_comm_max_points': 'point_limit_学习委员_max',
'study_comm_min_points': 'point_limit_学习委员_min',
'attendance_rep_max_points': 'point_limit_考勤委员_max',
'attendance_rep_min_points': 'point_limit_考勤委员_min',
'labor_rep_max_points': 'point_limit_劳动委员_max',
'labor_rep_min_points': 'point_limit_劳动委员_min',
'volunteer_rep_max_points': 'point_limit_志愿委员_max',
'volunteer_rep_min_points': 'point_limit_志愿委员_min',
'subject_teacher_max_points': 'point_limit_科任老师_max',
'subject_teacher_min_points': 'point_limit_科任老师_min'
};
var limitFields = Object.keys(limitFieldMap);
var toggleFields = [
'parent_account_enabled', 'parent_password_change_enabled', 'parent_view_attendance',
'parent_view_ranking', 'student_view_ranking', 'homework_management',
'attendance_management', 'cadre_homework'
];
async function loadSettings() {
var params = {};
if (window.CLASS_ID) {
params.class_id = window.CLASS_ID;
}
var result = await apiGet('/api/config/deduction-rules', params);
if (result && result.success && result.data) {
var d = result.data;
document.getElementById('setting_student_initial_points').value = d.STUDENT_INITIAL_POINTS || 60;
document.getElementById('setting_deduction_homework_not_submit').value = d.DEDUCTION_HOMEWORK_NOT_SUBMIT || 2;
document.getElementById('setting_deduction_homework_late').value = d.DEDUCTION_HOMEWORK_LATE || 1;
document.getElementById('setting_deduction_attendance_absent').value = d.DEDUCTION_ATTENDANCE_ABSENT || 3;
document.getElementById('setting_deduction_attendance_late').value = d.DEDUCTION_ATTENDANCE_LATE || 1;
document.getElementById('setting_deduction_attendance_leave').value = d.DEDUCTION_ATTENDANCE_LEAVE || 0;
}
}
async function loadPointLimits() {
var result = await apiGet('/api/class/point-limits');
if (result && result.success && result.data) {
var d = result.data.settings || result.data;
limitFields.forEach(function(key) {
var el = document.getElementById('limit_' + key);
var backendKey = limitFieldMap[key];
if (el) {
// 优先读取后端 point_limit_* key兼容旧 key
if (backendKey && d[backendKey] !== undefined) {
el.value = d[backendKey];
} else if (d[key] !== undefined) {
el.value = d[key];
}
}
});
}
}
async function loadRoleToggles() {
var result = await apiGet('/api/class/features');
if (result && result.success && result.data) {
var d = result.data.features || result.data;
toggleFields.forEach(function(key) {
var el = document.getElementById('toggle_' + key);
if (el) {
el.checked = d[key] === true || d[key] === 1 || d[key] === '1';
}
});
}
}
async function saveSettings() {
var settings = [
{ key: 'initial_points', value: document.getElementById('setting_student_initial_points').value },
{ key: 'deduction_homework_not_submit', value: document.getElementById('setting_deduction_homework_not_submit').value },
{ key: 'deduction_homework_late', value: document.getElementById('setting_deduction_homework_late').value },
{ key: 'deduction_attendance_absent', value: document.getElementById('setting_deduction_attendance_absent').value },
{ key: 'deduction_attendance_late', value: document.getElementById('setting_deduction_attendance_late').value },
{ key: 'deduction_attendance_leave', value: document.getElementById('setting_deduction_attendance_leave').value },
];
var success = true;
for (var i = 0; i < settings.length; i++) {
var s = settings[i];
var result = await apiPost('/api/class/settings', { setting_key: s.key, setting_value: s.value });
if (!result || !result.success) {
success = false;
}
}
if (success) {
showToast('班级设置已保存');
} else {
showToast('部分设置保存失败', 'error');
}
}
async function savePointLimits() {
var data = {};
limitFields.forEach(function(key) {
var el = document.getElementById('limit_' + key);
if (el) {
// 使用后端 point_limit_* key 保存,确保 conduct_service 能正确读取
var backendKey = limitFieldMap[key] || key;
data[backendKey] = el.value;
}
});
var result = await apiPost('/api/class/point-limits', data);
if (result && result.success) {
showToast('加减分限制已保存');
} else {
showToast(result && result.message ? result.message : '保存失败', 'error');
}
}
async function saveRoleToggles() {
var success = true;
for (var i = 0; i < toggleFields.length; i++) {
var key = toggleFields[i];
var el = document.getElementById('toggle_' + key);
if (el) {
var result = await apiPost('/api/class/features', {
feature_key: key,
enabled: el.checked ? 1 : 0
});
if (!result || !result.success) {
success = false;
}
}
}
if (success) {
showToast('功能开关已保存');
} else {
showToast('部分开关保存失败', 'error');
}
}
// ========== 周期重置设置 ==========
async function loadPeriodResetSettings() {
var result = await apiGet('/api/class/settings');
if (result && result.success && result.data && result.data.settings) {
var s = result.data.settings;
var freqSelect = document.getElementById('setting_reset_frequency');
freqSelect.value = s['reset_frequency'] || 'none';
toggleResetDay();
if (s['reset_day_of_week']) {
document.getElementById('setting_reset_day_of_week').value = s['reset_day_of_week'];
}
if (s['reset_day_of_month']) {
document.getElementById('setting_reset_day_of_month').value = s['reset_day_of_month'];
}
}
}
function toggleResetDay() {
var freq = document.getElementById('setting_reset_frequency').value;
document.getElementById('reset_day_of_week_group').style.display = (freq === 'weekly') ? 'block' : 'none';
document.getElementById('reset_day_of_month_group').style.display = (freq === 'monthly') ? 'block' : 'none';
}
async function savePeriodResetSettings() {
var freq = document.getElementById('setting_reset_frequency').value;
var settings = [
{ key: 'reset_frequency', value: freq }
];
if (freq === 'weekly') {
settings.push({ key: 'reset_day_of_week', value: document.getElementById('setting_reset_day_of_week').value });
} else if (freq === 'monthly') {
settings.push({ key: 'reset_day_of_month', value: document.getElementById('setting_reset_day_of_month').value });
}
var success = true;
for (var i = 0; i < settings.length; i++) {
var result = await apiPost('/api/class/settings', { setting_key: settings[i].key, setting_value: settings[i].value });
if (!result || !result.success) {
success = false;
}
}
if (success) {
showToast('周期重置设置已保存');
} else {
showToast('部分设置保存失败', 'error');
}
}
document.addEventListener('DOMContentLoaded', function() {
loadSettings();
loadPointLimits();
loadRoleToggles();
loadPeriodResetSettings();
});
</script>
<?php require_once __DIR__ . '/../includes/footer.php'; ?>