feat: 多班级版 v2.0 - Go后端重写 + 43轮代码审查
- 后端从 Python FastAPI 重写为 Go Gin(端口 56789) - 多班级完全隔离 - 超级管理员独立登录 - 课代表作业管理、排行榜分项排行 - 角色加减分上下限可配置 - 家长改密功能(可开关) - 周度/月度重置功能 - MySQL 5.7 兼容 - 43轮代码审查+全部修复 - Apache 2.0 许可证
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
# ===========================================
|
||||
# 班级操行分管理系统 - 前端配置
|
||||
# 多班级版班级管理系统 - 前端配置
|
||||
#
|
||||
# 开发者: Canglan
|
||||
# 联系方式: admin@sea-studio.top
|
||||
# 版权归属: Sea Network Technology Studio
|
||||
# 许可证: MIT License
|
||||
# 许可证: Apache License 2.0
|
||||
#
|
||||
# 版权所有 © Sea Network Technology Studio
|
||||
# ===========================================
|
||||
|
||||
# 后端API地址,修改为实际地址
|
||||
# 后端API地址(Go 后端默认端口 56789,通过 Nginx 反代后可直接使用域名)
|
||||
# 如果直接访问 Go 后端,格式为 http://your-server-ip:56789
|
||||
API_BASE_URL=https://your-api-domain.com
|
||||
|
||||
# API超时时间(秒)
|
||||
@@ -22,7 +23,7 @@ JWT_STORAGE_KEY=class_system_token
|
||||
USER_STORAGE_KEY=class_system_user
|
||||
|
||||
# 站点名称
|
||||
SITE_NAME=班级操行分管理系统
|
||||
SITE_NAME=多班级版班级管理系统
|
||||
|
||||
# 会话超时时间(分钟)
|
||||
SESSION_TIMEOUT=30
|
||||
@@ -32,4 +33,8 @@ SESSION_TIMEOUT=30
|
||||
ICP_ENABLED=false
|
||||
# ICP备案号
|
||||
ICP_NUMBER=京ICP备1234567890号-x
|
||||
|
||||
# 超级管理员独立登录路径(不含 /api 前缀,代码会自动拼接)
|
||||
SUPER_ADMIN_LOGIN_PATH=/super-admin
|
||||
|
||||
STUDENT_INITIAL_POINTS=60
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 管理端管理员管理
|
||||
* 多班级版班级管理系统 - 管理端管理员管理
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 管理端考勤管理
|
||||
* 多班级版班级管理系统 - 管理端考勤管理
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
125
frontend/admin/cadre_homework.php
Normal file
125
frontend/admin/cadre_homework.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?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']) || $_SESSION['user_type'] !== 'admin') {
|
||||
header('Location: /index.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
$role = $_SESSION['role'] ?? '';
|
||||
if ($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">
|
||||
<div class="action-bar">
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-primary" onclick="showPublishModal()">发布作业</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>作业标题</th>
|
||||
<th>科目</th>
|
||||
<th>截止日期</th>
|
||||
<th>描述</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="homeworkList">
|
||||
<tr><td colspan="5" style="text-align:center;">加载中...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination" id="pagination" style="display:none;">
|
||||
<button class="btn btn-sm" id="prevBtn" onclick="changePage(-1)">上一页</button>
|
||||
<span id="pageInfo">1 / 1</span>
|
||||
<button class="btn btn-sm" id="nextBtn" onclick="changePage(1)">下一页</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 发布作业模态框 -->
|
||||
<div id="publishModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>发布作业</h3>
|
||||
<button class="modal-close" onclick="closeModal('publishModal')">×</button>
|
||||
</div>
|
||||
<form id="publishForm" onsubmit="event.preventDefault(); submitHomework()">
|
||||
<div class="form-group">
|
||||
<label>作业标题 <span style="color:red;">*</span></label>
|
||||
<input type="text" id="hwTitle" required placeholder="例如:第三章练习">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>截止日期 <span style="color:red;">*</span></label>
|
||||
<input type="date" id="hwDeadline" required value="<?php echo date('Y-m-d'); ?>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>描述</label>
|
||||
<textarea id="hwDescription" rows="3" placeholder="选填,作业详细说明"></textarea>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary">发布</button>
|
||||
<button type="button" class="btn" onclick="closeModal('publishModal')">取消</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 缺交登记模态框 -->
|
||||
<div id="absentModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>登记缺交学生</h3>
|
||||
<button class="modal-close" onclick="closeModal('absentModal')">×</button>
|
||||
</div>
|
||||
<div id="absentStudentList"></div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" onclick="submitAbsent()">提交缺交记录</button>
|
||||
<button class="btn" onclick="closeModal('absentModal')">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.pagination .btn { padding: 6px 16px; font-size: 13px; }
|
||||
.pagination span { color: #666; font-size: 14px; }
|
||||
</style>
|
||||
|
||||
<script src="/assets/js/cadre-homework.js"></script>
|
||||
|
||||
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
|
||||
439
frontend/admin/class_settings.php
Normal file
439
frontend/admin/class_settings.php
Normal file
@@ -0,0 +1,439 @@
|
||||
<?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'; ?>
|
||||
214
frontend/admin/classes.php
Normal file
214
frontend/admin/classes.php
Normal 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()">×</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'; ?>
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 管理端操行分管理
|
||||
* 多班级版班级管理系统 - 管理端操行分管理
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
@@ -20,7 +20,7 @@ if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'admin') {
|
||||
$page_title = '操行分管理';
|
||||
$role = $_SESSION['role'] ?? '';
|
||||
|
||||
if (!in_array($role, ['班主任', '班长', '学习委员', '考勤委员', '劳动委员', '志愿委员'])) {
|
||||
if (!in_array($role, ['班主任', '班长', '学习委员', '考勤委员', '劳动委员', '志愿委员', '科任老师'])) {
|
||||
header('Location: /admin/dashboard.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 管理端首页
|
||||
* 多班级版班级管理系统 - 管理端首页
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 管理端历史记录
|
||||
* 多班级版班级管理系统 - 管理端历史记录
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
@@ -93,11 +93,12 @@ include __DIR__ . '/../includes/header.php';
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr id="historyTableHead">
|
||||
<th>时间</th>
|
||||
<th>学生</th>
|
||||
<th>分数变动</th>
|
||||
<th>类型</th>
|
||||
<th>分值</th>
|
||||
<th>原因</th>
|
||||
<th>学生</th>
|
||||
<th style="white-space: nowrap; min-width: 80px;">操作人</th>
|
||||
<th>时间</th>
|
||||
<?php if ($role === '班主任' || $role === '班长' || $role === '考勤委员'): ?>
|
||||
<th>操作</th>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 管理端作业扣分
|
||||
* 多班级版班级管理系统 - 管理端作业扣分
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 管理端修改密码
|
||||
* 多班级版班级管理系统 - 管理端修改密码
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
91
frontend/admin/rankings.php
Normal file
91
frontend/admin/rankings.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?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']) || !in_array($_SESSION['user_type'], ['admin', 'super_admin'])) {
|
||||
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>
|
||||
</div>
|
||||
|
||||
<div class="tab-bar">
|
||||
<button class="tab-btn active" onclick="switchTab('conduct', this)">操行分排行</button>
|
||||
<button class="tab-btn" onclick="switchTab('attendance', this)">考勤排行</button>
|
||||
<button class="tab-btn" onclick="switchTab('homework', this)">作业排行</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="table-wrapper">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>排名</th>
|
||||
<th>学号</th>
|
||||
<th>姓名</th>
|
||||
<th>分值</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="rankingList">
|
||||
<tr><td colspan="4" style="text-align:center;">加载中...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
}
|
||||
.tab-btn {
|
||||
padding: 10px 24px;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.tab-btn:hover {
|
||||
color: #333;
|
||||
}
|
||||
.tab-btn.active {
|
||||
color: #4a6cf7;
|
||||
border-bottom-color: #4a6cf7;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="/assets/js/rankings.js"></script>
|
||||
|
||||
<?php require_once __DIR__ . '/../includes/footer.php'; ?>
|
||||
@@ -1,18 +1,18 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 学期管理页面
|
||||
* 多班级版班级管理系统 - 学期管理页面
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config.php';
|
||||
|
||||
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'admin') {
|
||||
if (!isset($_SESSION['user_id']) || !in_array($_SESSION['user_type'], ['admin', 'super_admin'])) {
|
||||
header('Location: /index.php');
|
||||
exit();
|
||||
}
|
||||
@@ -30,6 +30,18 @@ include __DIR__ . '/../includes/header.php';
|
||||
<?php include __DIR__ . '/../includes/nav.php'; ?>
|
||||
|
||||
<div class="container">
|
||||
<!-- 周期重置 -->
|
||||
<div class="card">
|
||||
<h3>周期重置</h3>
|
||||
<p class="text-muted" style="margin-bottom:15px;">手动触发当前班级的周/月操行分重置(重置前会自动创建分数快照)</p>
|
||||
<div class="action-bar">
|
||||
<button class="btn btn-warning" onclick="confirmPeriodReset('weekly')">执行本周重置</button>
|
||||
<button class="btn btn-warning" onclick="confirmPeriodReset('monthly')">执行本月重置</button>
|
||||
<button class="btn btn-secondary" onclick="showPeriodArchives('weekly')">查看周归档</button>
|
||||
<button class="btn btn-secondary" onclick="showPeriodArchives('monthly')">查看月归档</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="action-bar">
|
||||
<button class="btn btn-primary" onclick="showCreateSemesterModal()">创建新学期</button>
|
||||
@@ -198,6 +210,54 @@ include __DIR__ . '/../includes/header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 周期重置确认模态框 -->
|
||||
<div id="periodResetModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3>确认周期重置</h3>
|
||||
<button class="modal-close" onclick="closeModal('periodResetModal')">×</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<p id="periodResetText" style="margin: 10px 0;"></p>
|
||||
<p style="color: #e74c3c; font-size: 13px; margin-top: 6px;">注意:重置前会自动保存当前操行分快照,重置后所有学生操行分将恢复为初始值。此操作不可撤销。</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-warning" onclick="executePeriodReset()">确认重置</button>
|
||||
<button type="button" class="btn" onclick="closeModal('periodResetModal')">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 周期归档数据查看模态框 -->
|
||||
<div id="periodArchivesModal" class="modal">
|
||||
<div class="modal-content" style="max-width: 600px;">
|
||||
<div class="modal-header">
|
||||
<h3 id="periodArchivesTitle">周期归档数据</h3>
|
||||
<button class="modal-close" onclick="closeModal('periodArchivesModal')">×</button>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>周期标签</th>
|
||||
<th>排名</th>
|
||||
<th>学号</th>
|
||||
<th>姓名</th>
|
||||
<th>操行分</th>
|
||||
<th>触发方式</th>
|
||||
<th>归档时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="periodArchivesList"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination" id="periodArchivePagination"></div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn" onclick="closeModal('periodArchivesModal')">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/modules/modal-utils.js"></script>
|
||||
<script src="/assets/js/semesters.js"></script>
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 管理端学生管理
|
||||
* 多班级版班级管理系统 - 管理端学生管理
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
@@ -48,7 +48,7 @@ include __DIR__ . '/../includes/header.php';
|
||||
<th>姓名</th>
|
||||
<th>宿舍号</th>
|
||||
<th>操行分</th>
|
||||
<?php if ($role === '班主任'): ?><th>家长手机号</th><?php endif; ?>
|
||||
<?php if ($role === '班主任'): ?><th>家长账号(推荐手机号)</th><?php endif; ?>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -99,7 +99,7 @@ include __DIR__ . '/../includes/header.php';
|
||||
<input type="text" id="studentName" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>家长手机号</label>
|
||||
<label>家长账号(推荐手机号)</label>
|
||||
<input type="tel" id="parentPhone" placeholder="11位手机号">
|
||||
<small>填写后将自动创建家长账号(密码同学生初始密码123456)</small>
|
||||
</div>
|
||||
@@ -133,7 +133,7 @@ include __DIR__ . '/../includes/header.php';
|
||||
<input type="text" id="editStudentName" required maxlength="50">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>家长手机号</label>
|
||||
<label>家长账号(推荐手机号)</label>
|
||||
<input type="text" id="editStudentPhone" maxlength="20">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 科目管理(已合并至作业管理页)
|
||||
* 多班级版班级管理系统 - 科目管理(已合并至作业管理页)
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
@@ -38,8 +38,8 @@ curl_setopt_array($ch, [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Content-Type: application/json'
|
||||
],
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => 0
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2
|
||||
]);
|
||||
|
||||
$apiResponse = curl_exec($ch);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - Session 退出清除接口
|
||||
* 多班级版班级管理系统 - Session 退出清除接口
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*
|
||||
@@ -38,6 +38,8 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
exit();
|
||||
}
|
||||
|
||||
// CSRF 风险说明:此接口仅清除 Session,无敏感数据操作。
|
||||
// 部署于同域 Nginx 反代下,浏览器同源策略已阻止跨域调用,实际风险较低。
|
||||
// 清除 Session
|
||||
$_SESSION = array();
|
||||
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
/**
|
||||
* 执行单个升级步骤(代理至后端 API)
|
||||
*/
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
require_once __DIR__ . '/../config.php';
|
||||
|
||||
// 验证登录和权限
|
||||
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'admin') {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// 验证登录和权限(admin 班主任 或 super_admin)
|
||||
if (!isset($_SESSION['user_id']) || !in_array($_SESSION['user_type'], ['admin', 'super_admin'])) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['success' => false, 'error' => '未授权']);
|
||||
exit();
|
||||
}
|
||||
|
||||
$userType = $_SESSION['user_type'];
|
||||
$role = $_SESSION['role'] ?? '';
|
||||
if ($role !== '班主任') {
|
||||
if ($userType === 'admin' && $role !== '班主任') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['success' => false, 'error' => '权限不足']);
|
||||
exit();
|
||||
@@ -27,7 +28,8 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
exit();
|
||||
}
|
||||
|
||||
$stepVersion = $_GET['version'] ?? '';
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$stepVersion = $input['version'] ?? '';
|
||||
if (empty($stepVersion)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'error' => '缺少版本号参数']);
|
||||
@@ -56,8 +58,8 @@ curl_setopt_array($ch, [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Content-Type: application/json'
|
||||
],
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => 0
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2
|
||||
]);
|
||||
|
||||
$apiResponse = curl_exec($ch);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - Session 保存接口
|
||||
* 多班级版班级管理系统 - Session 保存接口
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*
|
||||
@@ -27,7 +27,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(200);
|
||||
exit();
|
||||
}
|
||||
|
||||
// 只允许 POST 请求
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
@@ -38,6 +37,34 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
exit();
|
||||
}
|
||||
|
||||
// CSRF 防护:验证 Origin/Referer 头确保同源请求
|
||||
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||
$referer = $_SERVER['HTTP_REFERER'] ?? '';
|
||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||
$serverName = $_SERVER['SERVER_NAME'] ?? '';
|
||||
|
||||
if (!empty($origin)) {
|
||||
$parsedOrigin = parse_url($origin, PHP_URL_HOST);
|
||||
if ($parsedOrigin !== $host && $parsedOrigin !== $serverName) {
|
||||
http_response_code(403);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '跨域请求被拒绝'
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
} elseif (!empty($referer)) {
|
||||
$parsedReferer = parse_url($referer, PHP_URL_HOST);
|
||||
if ($parsedReferer !== $host && $parsedReferer !== $serverName) {
|
||||
http_response_code(403);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => '跨域请求被拒绝'
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
// 获取原始输入
|
||||
$input = file_get_contents('php://input');
|
||||
|
||||
@@ -82,7 +109,7 @@ if (!empty($missingFields)) {
|
||||
}
|
||||
|
||||
// 验证 user_type 是否合法
|
||||
$validUserTypes = ['student', 'parent', 'admin'];
|
||||
$validUserTypes = ['student', 'parent', 'admin', 'super_admin'];
|
||||
if (!in_array($data['user_type'], $validUserTypes)) {
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
@@ -115,8 +142,8 @@ curl_setopt_array($ch, [
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Content-Type: application/json'
|
||||
],
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => 0
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2
|
||||
]);
|
||||
|
||||
$apiResponse = curl_exec($ch);
|
||||
@@ -153,18 +180,23 @@ if ($tokenUserId === null || intval($tokenUserId) !== intval($data['user_id']))
|
||||
exit();
|
||||
}
|
||||
|
||||
// 设置 Session 变量
|
||||
$_SESSION['user_id'] = $data['user_id'];
|
||||
$_SESSION['user_type'] = $data['user_type'];
|
||||
$_SESSION['username'] = $data['username'];
|
||||
$_SESSION['real_name'] = $data['real_name'] ?? '';
|
||||
$_SESSION['role'] = $data['role'] ?? '';
|
||||
// 从后端 JWT 解析权威数据(不信任客户端传入的 user_type/role)
|
||||
$tokenData_user = $tokenData['data'];
|
||||
// 登录成功后重新生成 Session ID,防止 Session 固定攻击
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['user_id'] = intval($tokenData_user['user_id']);
|
||||
$_SESSION['user_type'] = $tokenData_user['user_type'];
|
||||
$_SESSION['username'] = $tokenData_user['username'];
|
||||
$_SESSION['real_name'] = $tokenData_user['real_name'] ?? '';
|
||||
$_SESSION['role'] = $tokenData_user['role'] ?? '';
|
||||
$_SESSION['class_id'] = $tokenData_user['class_id'] ?? null;
|
||||
$_SESSION['class_name'] = $tokenData_user['class_name'] ?? '';
|
||||
$_SESSION['login_time'] = time();
|
||||
$_SESSION['jwt_token'] = $token;
|
||||
|
||||
// 如果是学生,额外设置 student_id
|
||||
if ($data['user_type'] === 'student') {
|
||||
if (empty($data['student_id'])) {
|
||||
// 如果是学生,额外设置 student_id(仅从 JWT 解析,不信任客户端传入值)
|
||||
if ($_SESSION['user_type'] === 'student') {
|
||||
$studentId = $tokenData_user['student_id'] ?? null;
|
||||
if (empty($studentId)) {
|
||||
http_response_code(400);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
@@ -172,7 +204,7 @@ if ($data['user_type'] === 'student') {
|
||||
]);
|
||||
exit();
|
||||
}
|
||||
$_SESSION['student_id'] = $data['student_id'];
|
||||
$_SESSION['student_id'] = $studentId;
|
||||
}
|
||||
|
||||
// 保存 Session
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 管理端样式
|
||||
* 多班级版班级管理系统 - 管理端样式
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 全局样式
|
||||
* 多班级版班级管理系统 - 全局样式
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
@@ -691,6 +691,10 @@ tr:hover {
|
||||
background: #ed8936;
|
||||
}
|
||||
|
||||
.toast-info {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 管理员管理页JS
|
||||
* 多班级版班级管理系统 - 管理员管理页JS
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 考勤管理页JS
|
||||
* 多班级版班级管理系统 - 考勤管理页JS
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
|
||||
159
frontend/assets/js/cadre-homework.js
Normal file
159
frontend/assets/js/cadre-homework.js
Normal file
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* 多班级版班级管理系统 - 课代表作业管理JS
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var currentPage = 1;
|
||||
var pageSize = 20;
|
||||
var currentAssignmentId = null;
|
||||
|
||||
async function loadHomework(page) {
|
||||
var res = await apiGet('/api/cadre/homework', { page: page, page_size: pageSize });
|
||||
if (res && res.success && res.data) {
|
||||
var items = res.data.items || res.data.records || [];
|
||||
var total = res.data.total || 0;
|
||||
var html = '';
|
||||
if (items.length === 0) {
|
||||
html = '<tr><td colspan="5" style="text-align:center;">暂无作业记录</td></tr>';
|
||||
} else {
|
||||
items.forEach(function(item) {
|
||||
html += '<tr>' +
|
||||
'<td>' + escapeHtml(item.title || '-') + '</td>' +
|
||||
'<td>' + escapeHtml(item.subject_name || '-') + '</td>' +
|
||||
'<td>' + formatDate(item.deadline) + '</td>' +
|
||||
'<td>' + escapeHtml(item.description || '-') + '</td>' +
|
||||
'<td><button class="btn btn-sm btn-outline" onclick="showAbsentModal(' + item.assignment_id + ')">登记缺交</button></td>' +
|
||||
'</tr>';
|
||||
});
|
||||
}
|
||||
document.getElementById('homeworkList').innerHTML = html;
|
||||
|
||||
var totalPages = Math.ceil(total / pageSize);
|
||||
if (totalPages > 1) {
|
||||
document.getElementById('pagination').style.display = 'flex';
|
||||
document.getElementById('pageInfo').textContent = page + ' / ' + totalPages;
|
||||
document.getElementById('prevBtn').disabled = page <= 1;
|
||||
document.getElementById('nextBtn').disabled = page >= totalPages;
|
||||
} else {
|
||||
document.getElementById('pagination').style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.changePage = function(delta) {
|
||||
currentPage += delta;
|
||||
loadHomework(currentPage);
|
||||
};
|
||||
|
||||
window.showPublishModal = function() {
|
||||
document.getElementById('publishForm').reset();
|
||||
document.getElementById('hwDeadline').value = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('publishModal').style.display = 'flex';
|
||||
};
|
||||
|
||||
window.submitHomework = async function() {
|
||||
var title = document.getElementById('hwTitle').value.trim();
|
||||
var deadline = document.getElementById('hwDeadline').value;
|
||||
var description = document.getElementById('hwDescription').value.trim();
|
||||
|
||||
if (!title) {
|
||||
showToast('请填写作业标题', 'error');
|
||||
return;
|
||||
}
|
||||
if (!deadline) {
|
||||
showToast('请选择截止日期', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
var res = await apiPost('/api/cadre/homework', {
|
||||
title: title,
|
||||
deadline: deadline,
|
||||
description: description
|
||||
});
|
||||
|
||||
if (res && res.success) {
|
||||
showToast('作业发布成功');
|
||||
closeModal('publishModal');
|
||||
loadHomework(currentPage);
|
||||
} else {
|
||||
showToast(res && res.message ? res.message : '发布失败', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
window.showAbsentModal = async function(assignmentId) {
|
||||
currentAssignmentId = assignmentId;
|
||||
var res = await apiGet('/api/admin/students', { page_size: 1000 });
|
||||
if (res && res.success && res.data) {
|
||||
var students = res.data.students || res.data.items || [];
|
||||
var html = '<div class="form-group"><label>选择缺交学生</label></div>';
|
||||
if (students.length === 0) {
|
||||
html += '<p style="text-align:center;padding:20px;">暂无学生数据</p>';
|
||||
} else {
|
||||
html += '<div class="table-wrapper"><table class="table"><thead><tr>' +
|
||||
'<th><input type="checkbox" id="selectAllAbsent" onchange="toggleAllAbsent(this)"></th>' +
|
||||
'<th>学号</th><th>姓名</th></tr></thead><tbody>';
|
||||
students.forEach(function(s) {
|
||||
html += '<tr>' +
|
||||
'<td><input type="checkbox" class="absent-checkbox" data-id="' + s.student_id + '"></td>' +
|
||||
'<td>' + escapeHtml(s.student_no) + '</td>' +
|
||||
'<td>' + escapeHtml(s.name) + '</td>' +
|
||||
'</tr>';
|
||||
});
|
||||
html += '</tbody></table></div>';
|
||||
}
|
||||
document.getElementById('absentStudentList').innerHTML = html;
|
||||
document.getElementById('absentModal').style.display = 'flex';
|
||||
} else {
|
||||
showToast('获取学生列表失败', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
window.toggleAllAbsent = function(el) {
|
||||
var checkboxes = document.querySelectorAll('.absent-checkbox');
|
||||
checkboxes.forEach(function(cb) { cb.checked = el.checked; });
|
||||
};
|
||||
|
||||
window.submitAbsent = async function() {
|
||||
var checkboxes = document.querySelectorAll('.absent-checkbox:checked');
|
||||
if (checkboxes.length === 0) {
|
||||
showToast('请选择至少一名缺交学生', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
var studentIds = [];
|
||||
checkboxes.forEach(function(cb) {
|
||||
studentIds.push(parseInt(cb.getAttribute('data-id')));
|
||||
});
|
||||
|
||||
var hwDeduct = window.DEDUCTION_HOMEWORK_NOT_SUBMIT || 2;
|
||||
var res = await apiPost('/api/cadre/conduct/add', {
|
||||
student_ids: studentIds,
|
||||
points_change: -hwDeduct,
|
||||
reason: '作业未提交',
|
||||
related_type: 'homework'
|
||||
});
|
||||
|
||||
if (res && res.success) {
|
||||
showToast('已登记 ' + studentIds.length + ' 名学生缺交');
|
||||
closeModal('absentModal');
|
||||
} else {
|
||||
showToast(res && res.message ? res.message : '提交失败', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
window.closeModal = function(id) {
|
||||
document.getElementById(id).style.display = 'none';
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadHomework(currentPage);
|
||||
});
|
||||
|
||||
})();
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 公共JS
|
||||
* 多班级版班级管理系统 - 公共JS
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
@@ -18,7 +18,7 @@ function getUserInfo() {
|
||||
if (!userStr) return null;
|
||||
try {
|
||||
return JSON.parse(userStr);
|
||||
} catch {
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -191,11 +191,13 @@ async function logout() {
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '\x26amp;')
|
||||
.replace(/</g, '\x26lt;')
|
||||
.replace(/>/g, '\x26gt;')
|
||||
.replace(/"/g, '\x26quot;')
|
||||
.replace(/'/g, '\x26#x27;');
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/`/g, '`')
|
||||
.replace(/\//g, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -383,21 +385,19 @@ document.addEventListener('click', function(e) {
|
||||
}
|
||||
});
|
||||
|
||||
// 全局textarea键盘事件:Enter提交表单,Ctrl+Enter换行
|
||||
// 全局textarea键盘事件:Ctrl+Enter提交表单,Enter换行(默认行为)
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.target.tagName !== 'TEXTAREA') return;
|
||||
|
||||
if (e.key === 'Enter' && !e.ctrlKey && !e.shiftKey && !e.metaKey) {
|
||||
// Enter键提交表单
|
||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
// Ctrl+Enter / Cmd+Enter 提交表单
|
||||
e.preventDefault();
|
||||
var form = e.target.closest('form');
|
||||
if (form) {
|
||||
// 触发form的submit事件
|
||||
var submitEvent = new Event('submit', { cancelable: true, bubbles: true });
|
||||
form.dispatchEvent(submitEvent);
|
||||
}
|
||||
}
|
||||
// Ctrl+Enter和Shift+Enter保持默认换行行为(不拦截)
|
||||
});
|
||||
|
||||
window.selectDeductionType = function(points, reason) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 操行分管理页JS
|
||||
* 多班级版班级管理系统 - 操行分管理页JS
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
@@ -20,7 +20,7 @@ async function loadStudents() {
|
||||
<td>${escapeHtml(student.student_no)}</td>
|
||||
<td><a href="/admin/history.php?student_id=${student.student_id}" class="link">${escapeHtml(student.name)}</a></td>
|
||||
<td>${student.total_points}</td>
|
||||
<td><button class="btn btn-sm btn-outline" onclick="showSinglePointsModal(${student.student_id}, '${escapeHtml(student.name)}')">加减分</button></td>
|
||||
<td><button class="btn btn-sm btn-outline js-single-points" data-student-id="${student.student_id}" data-student-name="${escapeHtml(student.name)}">加减分</button></td>
|
||||
</tr>`;
|
||||
});
|
||||
if (res.data.students.length === 0) {
|
||||
@@ -39,7 +39,7 @@ function showSinglePointsModal(studentId, studentName) {
|
||||
}
|
||||
|
||||
async function exportMoralityRecords() {
|
||||
showToast('正在导出德育分记录...', 'info');
|
||||
showToast('正在导出操行分记录...', 'info');
|
||||
|
||||
try {
|
||||
const studentsRes = await apiGet('/api/admin/students', { page_size: 1000 });
|
||||
@@ -54,13 +54,20 @@ async function exportMoralityRecords() {
|
||||
return;
|
||||
}
|
||||
|
||||
const historyRes = await apiGet('/api/admin/conduct/history', { page: 1, page_size: 1000 });
|
||||
if (!historyRes || !historyRes.success) {
|
||||
showToast('获取历史记录失败', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const allRecords = historyRes.data.records || [];
|
||||
const allRecords = [];
|
||||
let page = 1;
|
||||
let totalPages = 1;
|
||||
do {
|
||||
const historyRes = await apiGet('/api/admin/conduct/history', { page: page, page_size: 500 });
|
||||
if (!historyRes || !historyRes.success) {
|
||||
showToast('获取历史记录失败', 'error');
|
||||
return;
|
||||
}
|
||||
const records = historyRes.data.records || [];
|
||||
allRecords.push(...records);
|
||||
totalPages = historyRes.data.total_pages || 1;
|
||||
page++;
|
||||
} while (page <= totalPages);
|
||||
|
||||
const recordsByStudent = {};
|
||||
allRecords.forEach(record => {
|
||||
@@ -90,7 +97,7 @@ async function exportMoralityRecords() {
|
||||
if (field === null || field === undefined) return '';
|
||||
let str = String(field).replace(/[\r\n]+/g, ' ');
|
||||
str = str.replace(/"/g, '""');
|
||||
if (/[\,\;\"\s]/.test(str)) {
|
||||
if (/[\,\"\s]/.test(str)) {
|
||||
str = '"' + str + '"';
|
||||
}
|
||||
return str;
|
||||
@@ -106,7 +113,7 @@ async function exportMoralityRecords() {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `德育分记录_${new Date().toISOString().slice(0,10)}.csv`;
|
||||
link.download = `操行分记录_${new Date().toISOString().slice(0,10)}.csv`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
@@ -119,7 +126,7 @@ async function exportMoralityRecords() {
|
||||
}
|
||||
}
|
||||
// 宿舍集体加分相关
|
||||
var dormitoryStudentIds = [];
|
||||
let dormitoryStudentIds = [];
|
||||
|
||||
async function showDormitoryPointsModal() {
|
||||
dormitoryStudentIds = [];
|
||||
@@ -196,6 +203,11 @@ async function submitDormitoryPoints() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Math.abs(pointsChange) > 100) {
|
||||
showToast('分值绝对值不能超过100', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!reason.trim()) {
|
||||
showToast('请填写原因', 'error');
|
||||
return;
|
||||
@@ -204,7 +216,8 @@ async function submitDormitoryPoints() {
|
||||
const data = {
|
||||
student_ids: dormitoryStudentIds,
|
||||
points_change: pointsChange,
|
||||
reason: reason
|
||||
reason: reason,
|
||||
related_type: 'manual'
|
||||
};
|
||||
|
||||
const res = await apiPost('/api/admin/conduct/add', data);
|
||||
@@ -220,6 +233,16 @@ async function submitDormitoryPoints() {
|
||||
|
||||
loadStudents();
|
||||
|
||||
document.getElementById('studentList').addEventListener('click', function(e) {
|
||||
const btn = e.target.closest('.js-single-points');
|
||||
if (btn) {
|
||||
showSinglePointsModal(
|
||||
parseInt(btn.dataset.studentId, 10),
|
||||
btn.dataset.studentName
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
window.loadStudents = loadStudents;
|
||||
window.showSinglePointsModal = showSinglePointsModal;
|
||||
window.exportMoralityRecords = exportMoralityRecords;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 管理端首页JS
|
||||
* 多班级版班级管理系统 - 管理端首页JS
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 历史记录页JS
|
||||
* 多班级版班级管理系统 - 历史记录页JS
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
@@ -15,11 +15,14 @@ const currentUserId = window.PAGE_CONFIG.userId;
|
||||
let currentHistoryPage = 1;
|
||||
let totalHistoryPages = 1;
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
var el = document.createElement('span');
|
||||
el.appendChild(document.createTextNode(str));
|
||||
return el.innerHTML;
|
||||
function getTypeLabel(relatedType) {
|
||||
if (!relatedType) return '操行';
|
||||
switch (relatedType) {
|
||||
case 'conduct': return '操行';
|
||||
case 'homework': return '作业';
|
||||
case 'attendance': return '考勤';
|
||||
default: return relatedType;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStudentsForSelect() {
|
||||
@@ -35,9 +38,9 @@ async function loadStudentsForSelect() {
|
||||
|
||||
// 加载科目下拉列表
|
||||
async function loadSubjectsForFilter() {
|
||||
var subjectSelect = document.getElementById('historySubjectFilter');
|
||||
let subjectSelect = document.getElementById('historySubjectFilter');
|
||||
if (!subjectSelect) return;
|
||||
var res = await apiGet('/api/subject/list', { is_active: true });
|
||||
let res = await apiGet('/api/subject/list', { is_active: true });
|
||||
if (res && res.success && res.data && res.data.subjects) {
|
||||
let html = '<option value="">全部科目</option>';
|
||||
res.data.subjects.forEach(s => {
|
||||
@@ -49,16 +52,16 @@ async function loadSubjectsForFilter() {
|
||||
|
||||
// 筛选学生时自动取消合并记录
|
||||
function onStudentFilterChange() {
|
||||
var studentId = document.getElementById('historyStudentId').value;
|
||||
let studentId = document.getElementById('historyStudentId').value;
|
||||
if (studentId) {
|
||||
var grouped = document.getElementById('historyGrouped');
|
||||
let grouped = document.getElementById('historyGrouped');
|
||||
if (grouped) grouped.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 科目筛选变化时,取消扣分类型筛选(互斥)
|
||||
function onSubjectFilterChange() {
|
||||
var subjectVal = document.getElementById('historySubjectFilter').value;
|
||||
let subjectVal = document.getElementById('historySubjectFilter').value;
|
||||
if (subjectVal) {
|
||||
document.getElementById('historyReasonFilter').value = '';
|
||||
}
|
||||
@@ -66,8 +69,8 @@ function onSubjectFilterChange() {
|
||||
|
||||
// 折叠/展开筛选面板
|
||||
function toggleFilterPanel() {
|
||||
var panel = document.getElementById('advancedFilters');
|
||||
var btn = document.getElementById('filterToggleBtn');
|
||||
let panel = document.getElementById('advancedFilters');
|
||||
let btn = document.getElementById('filterToggleBtn');
|
||||
if (!panel || !btn) return;
|
||||
if (panel.style.display === 'none') {
|
||||
panel.style.display = 'block';
|
||||
@@ -81,19 +84,19 @@ function toggleFilterPanel() {
|
||||
async function loadHistory(page) {
|
||||
page = page || 1;
|
||||
currentHistoryPage = page;
|
||||
var startDate = document.getElementById('historyStartDate').value;
|
||||
var endDate = document.getElementById('historyEndDate').value;
|
||||
var studentId = document.getElementById('historyStudentId').value;
|
||||
var reasonFilter = document.getElementById('historyReasonFilter').value;
|
||||
var subjectFilter = document.getElementById('historySubjectFilter').value;
|
||||
var reasonSearch = document.getElementById('historyReasonSearch').value.trim();
|
||||
var isGrouped = document.getElementById('historyGrouped').checked;
|
||||
var statusFilter = document.getElementById('historyStatusFilter') ? document.getElementById('historyStatusFilter').value : '';
|
||||
let startDate = document.getElementById('historyStartDate').value;
|
||||
let endDate = document.getElementById('historyEndDate').value;
|
||||
let studentId = document.getElementById('historyStudentId').value;
|
||||
let reasonFilter = document.getElementById('historyReasonFilter').value;
|
||||
let subjectFilter = document.getElementById('historySubjectFilter').value;
|
||||
let reasonSearch = document.getElementById('historyReasonSearch').value.trim();
|
||||
let isGrouped = document.getElementById('historyGrouped').checked;
|
||||
let statusFilter = document.getElementById('historyStatusFilter') ? document.getElementById('historyStatusFilter').value : '';
|
||||
|
||||
// 筛选学生时强制取消合并
|
||||
if (studentId) isGrouped = false;
|
||||
|
||||
var params = {
|
||||
let params = {
|
||||
page: page, page_size: 20,
|
||||
start_date: startDate,
|
||||
end_date: endDate
|
||||
@@ -111,37 +114,38 @@ async function loadHistory(page) {
|
||||
if (isGrouped) params.grouped = true;
|
||||
if (statusFilter !== '') params.is_revoked = parseInt(statusFilter);
|
||||
|
||||
var res = await apiGet('/api/admin/conduct/history', params);
|
||||
let res = await apiGet('/api/admin/conduct/history', params);
|
||||
|
||||
if (res && res.success) {
|
||||
var nowrapStyle = ' style="white-space:nowrap;min-width:80px;"';
|
||||
var headHtml = '';
|
||||
let nowrapStyle = ' style="white-space:nowrap;min-width:80px;"';
|
||||
let headHtml = '';
|
||||
if (isGrouped) {
|
||||
headHtml = '<th>时间</th><th>原因</th><th>分值</th><th' + nowrapStyle + '>操作人</th><th>涉及学生</th>';
|
||||
headHtml = '<th>类型</th><th>分值</th><th>原因</th><th>学生名单</th><th' + nowrapStyle + '>操作人</th><th>时间</th>';
|
||||
if (role === '班主任' || role === '班长') {
|
||||
headHtml += '<th>操作</th>';
|
||||
}
|
||||
} else {
|
||||
headHtml = '<th>时间</th><th>学生</th><th>分数变动</th><th>原因</th><th' + nowrapStyle + '>操作人</th>';
|
||||
headHtml = '<th>类型</th><th>分值</th><th>原因</th><th>学生</th><th' + nowrapStyle + '>操作人</th><th>时间</th>';
|
||||
if (role === '班主任' || role === '班长' || role === '考勤委员') {
|
||||
headHtml += '<th>操作</th>';
|
||||
}
|
||||
}
|
||||
document.getElementById('historyTableHead').innerHTML = headHtml;
|
||||
|
||||
var html = '';
|
||||
let html = '';
|
||||
if (isGrouped) {
|
||||
res.data.records.forEach(function(record) {
|
||||
var pointsClass = record.points_change > 0 ? 'plus' : 'minus';
|
||||
var names = record.student_names || '';
|
||||
var allRevoked = record.all_revoked;
|
||||
var revokedStyle = allRevoked ? ' style="opacity:0.5;text-decoration:line-through;"' : '';
|
||||
let pointsClass = record.points_change > 0 ? 'plus' : 'minus';
|
||||
let names = record.student_names || '';
|
||||
let allRevoked = record.all_revoked;
|
||||
let revokedStyle = allRevoked ? ' style="opacity:0.5;text-decoration:line-through;"' : '';
|
||||
html += '<tr' + revokedStyle + '>' +
|
||||
'<td class="history-time">' + formatDateTime(record.created_at) + '</td>' +
|
||||
'<td class="history-reason">' + escapeHtml(record.reason) + '</td>' +
|
||||
'<td>' + escapeHtml(getTypeLabel(record.related_type)) + '</td>' +
|
||||
'<td class="' + pointsClass + '">' + (record.points_change > 0 ? '+' : '') + record.points_change + '×' + record.student_count + '</td>' +
|
||||
'<td class="history-reason">' + escapeHtml(record.reason) + '</td>' +
|
||||
'<td class="history-students">' + escapeHtml(names) + '</td>' +
|
||||
'<td>' + escapeHtml(record.recorder_name || '') + '</td>' +
|
||||
'<td class="history-students">' + escapeHtml(names) + '</td>';
|
||||
'<td class="history-time">' + formatDateTime(record.created_at) + '</td>';
|
||||
if (role === '班主任' || role === '班长') {
|
||||
if (allRevoked) {
|
||||
html += '<td><span class="text-muted">已撤销</span></td>';
|
||||
@@ -152,29 +156,30 @@ async function loadHistory(page) {
|
||||
html += '</tr>';
|
||||
});
|
||||
if (res.data.records.length === 0) {
|
||||
var colSpan = (role === '班主任' || role === '班长') ? 6 : 5;
|
||||
let colSpan = (role === '班主任' || role === '班长') ? 7 : 6;
|
||||
html = '<tr><td colspan="' + colSpan + '" style="text-align:center;">暂无记录</td></tr>';
|
||||
}
|
||||
} else {
|
||||
res.data.records.forEach(function(record) {
|
||||
var pointsClass = record.points_change > 0 ? 'plus' : 'minus';
|
||||
var revokedStyle = record.is_revoked == 1 ? ' style="opacity:0.5;text-decoration:line-through;"' : '';
|
||||
let pointsClass = record.points_change > 0 ? 'plus' : 'minus';
|
||||
let revokedStyle = record.is_revoked == 1 ? ' style="opacity:0.5;text-decoration:line-through;"' : '';
|
||||
html += '<tr' + revokedStyle + '>' +
|
||||
'<td class="history-time">' + formatDateTime(record.created_at) + '</td>' +
|
||||
'<td>' + escapeHtml(record.student_name) + '</td>' +
|
||||
'<td>' + escapeHtml(getTypeLabel(record.related_type)) + '</td>' +
|
||||
'<td class="' + pointsClass + '">' + (record.points_change > 0 ? '+' : '') + record.points_change + '</td>' +
|
||||
'<td class="history-reason">' + escapeHtml(record.reason) + '</td>' +
|
||||
'<td>' + escapeHtml(record.recorder_name) + '</td>';
|
||||
'<td>' + escapeHtml(record.student_name) + '</td>' +
|
||||
'<td>' + escapeHtml(record.recorder_name) + '</td>' +
|
||||
'<td class="history-time">' + formatDateTime(record.created_at) + '</td>';
|
||||
if (role === '班主任') {
|
||||
if (record.is_revoked == 1) {
|
||||
var revokerInfo = record.revoker_name ? '由 ' + escapeHtml(record.revoker_name) + ' 撤销' : '已撤销';
|
||||
let revokerInfo = record.revoker_name ? '由 ' + escapeHtml(record.revoker_name) + ' 撤销' : '已撤销';
|
||||
html += '<td><span class="text-muted" style="margin-right:4px;">' + revokerInfo + '</span><button class="btn btn-sm btn-outline" onclick="restoreRecord(' + record.record_id + ')">反撤销</button></td>';
|
||||
} else {
|
||||
html += '<td><button class="btn btn-sm btn-outline" onclick="revokeRecord(' + record.record_id + ')">撤销</button></td>';
|
||||
}
|
||||
} else if (role === '班长') {
|
||||
if (record.is_revoked == 1) {
|
||||
var revokerInfo = record.revoker_name ? '由 ' + escapeHtml(record.revoker_name) + ' 撤销' : '已撤销';
|
||||
let revokerInfo = record.revoker_name ? '由 ' + escapeHtml(record.revoker_name) + ' 撤销' : '已撤销';
|
||||
html += '<td><span class="text-muted">' + revokerInfo + '</span></td>';
|
||||
} else {
|
||||
html += '<td><button class="btn btn-sm btn-outline" onclick="revokeRecord(' + record.record_id + ')">撤销</button></td>';
|
||||
@@ -192,7 +197,7 @@ async function loadHistory(page) {
|
||||
});
|
||||
|
||||
if (res.data.records.length === 0) {
|
||||
var colSpan = (role === '班主任' || role === '班长' || role === '考勤委员') ? 6 : 5;
|
||||
let colSpan = (role === '班主任' || role === '班长' || role === '考勤委员') ? 7 : 6;
|
||||
html = '<tr><td colspan="' + colSpan + '" style="text-align:center;">暂无记录</td></tr>';
|
||||
}
|
||||
}
|
||||
@@ -211,17 +216,17 @@ function renderHistoryPagination() {
|
||||
}
|
||||
|
||||
async function exportHistoryRecords() {
|
||||
var startDate = document.getElementById('historyStartDate').value;
|
||||
var endDate = document.getElementById('historyEndDate').value;
|
||||
var studentId = document.getElementById('historyStudentId').value;
|
||||
let startDate = document.getElementById('historyStartDate').value;
|
||||
let endDate = document.getElementById('historyEndDate').value;
|
||||
let studentId = document.getElementById('historyStudentId').value;
|
||||
|
||||
showToast('正在导出历史记录...', 'info');
|
||||
|
||||
try {
|
||||
var reasonFilter = document.getElementById('historyReasonFilter').value;
|
||||
var subjectFilter = document.getElementById('historySubjectFilter').value;
|
||||
var reasonSearch = document.getElementById('historyReasonSearch').value.trim();
|
||||
var params = { page: 1, page_size: 1000 };
|
||||
let reasonFilter = document.getElementById('historyReasonFilter').value;
|
||||
let subjectFilter = document.getElementById('historySubjectFilter').value;
|
||||
let reasonSearch = document.getElementById('historyReasonSearch').value.trim();
|
||||
let params = { page: 1, page_size: 1000 };
|
||||
if (startDate) params.start_date = startDate;
|
||||
if (endDate) params.end_date = endDate;
|
||||
if (studentId) params.student_id = studentId;
|
||||
@@ -232,24 +237,31 @@ async function exportHistoryRecords() {
|
||||
}
|
||||
if (reasonSearch) params.reason_search = reasonSearch;
|
||||
|
||||
var res = await apiGet('/api/admin/conduct/history', params);
|
||||
let res = await apiGet('/api/admin/conduct/history', params);
|
||||
if (res && res.success && res.data.records) {
|
||||
var records = res.data.records;
|
||||
let records = res.data.records;
|
||||
if (records.length === 0) {
|
||||
showToast('没有找到记录', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
var csv = '\uFEFF';
|
||||
function csvField(val) {
|
||||
let s = String(val == null ? '' : val);
|
||||
if (s.indexOf(',') >= 0 || s.indexOf('"') >= 0 || s.indexOf('\n') >= 0) {
|
||||
return '"' + s.replace(/"/g, '""') + '"';
|
||||
}
|
||||
return s;
|
||||
}
|
||||
let csv = '\uFEFF';
|
||||
csv += '时间,学号,姓名,分数变动,原因,操作人\n';
|
||||
records.forEach(function(r) {
|
||||
if (r.is_revoked == 1) return;
|
||||
csv += (r.created_at || '') + ',' + (r.student_no || '') + ',' + (r.student_name || '') + ',' + (r.points_change > 0 ? '+' : '') + r.points_change + ',' + (r.reason || '').replace(/,/g, ';') + ',' + (r.recorder_name || '') + '\n';
|
||||
csv += csvField(r.created_at) + ',' + csvField(r.student_no) + ',' + csvField(r.student_name) + ',' + csvField((r.points_change > 0 ? '+' : '') + r.points_change) + ',' + csvField(r.reason) + ',' + csvField(r.recorder_name) + '\n';
|
||||
});
|
||||
|
||||
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
var url = URL.createObjectURL(blob);
|
||||
var link = document.createElement('a');
|
||||
let blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
let url = URL.createObjectURL(blob);
|
||||
let link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = '历史记录_' + new Date().toISOString().slice(0,10) + '.csv';
|
||||
document.body.appendChild(link);
|
||||
@@ -273,21 +285,21 @@ async function batchRevokeGrouped(reason, pointsChange, recorderName, createdAt)
|
||||
showToast('正在批量撤销...', 'info');
|
||||
|
||||
try {
|
||||
var params = {
|
||||
let params = {
|
||||
page: 1, page_size: 1000,
|
||||
start_date: document.getElementById('historyStartDate').value,
|
||||
end_date: document.getElementById('historyEndDate').value,
|
||||
reason_prefix: reason.substring(0, 4),
|
||||
reason_prefix: reason,
|
||||
grouped: false
|
||||
};
|
||||
|
||||
var res = await apiGet('/api/admin/conduct/history', params);
|
||||
let res = await apiGet('/api/admin/conduct/history', params);
|
||||
if (!res || !res.success || !res.data.records) {
|
||||
showToast('查询记录失败', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
var matchedIds = [];
|
||||
let matchedIds = [];
|
||||
res.data.records.forEach(function(r) {
|
||||
if (r.reason === reason && r.points_change === pointsChange && r.is_revoked == 0) {
|
||||
matchedIds.push(r.record_id);
|
||||
@@ -299,7 +311,7 @@ async function batchRevokeGrouped(reason, pointsChange, recorderName, createdAt)
|
||||
return;
|
||||
}
|
||||
|
||||
var revokeRes = await apiPost('/api/admin/conduct/batch-revoke', { record_ids: matchedIds });
|
||||
let revokeRes = await apiPost('/api/admin/conduct/batch-revoke', { record_ids: matchedIds });
|
||||
if (revokeRes && revokeRes.success) {
|
||||
showToast('批量撤销完成: ' + (revokeRes.data ? revokeRes.data.success_count : 0) + '条成功');
|
||||
loadHistory(currentHistoryPage);
|
||||
@@ -313,8 +325,8 @@ async function batchRevokeGrouped(reason, pointsChange, recorderName, createdAt)
|
||||
|
||||
// 初始化:并行加载学生和科目列表,然后加载历史记录
|
||||
Promise.all([loadStudentsForSelect(), loadSubjectsForFilter()]).then(function() {
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
var preStudentId = urlParams.get('student_id');
|
||||
let urlParams = new URLSearchParams(window.location.search);
|
||||
let preStudentId = urlParams.get('student_id');
|
||||
if (preStudentId) {
|
||||
document.getElementById('historyStudentId').value = preStudentId;
|
||||
onStudentFilterChange();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 作业扣分页JS
|
||||
* 多班级版班级管理系统 - 作业扣分页JS
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
@@ -187,7 +187,7 @@ async function submitAddSubject() {
|
||||
}
|
||||
|
||||
async function toggleSubjectStatus(subjectId, enable) {
|
||||
const res = await apiPut(`/api/subject/update/${subjectId}`, { is_active: enable });
|
||||
const res = await apiPut(`/api/subject/toggle/${subjectId}`, { is_active: enable });
|
||||
if (res && res.success) {
|
||||
showToast(enable ? '科目已启用' : '科目已禁用');
|
||||
loadSubjectList();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 管理员管理函数
|
||||
* 多班级版班级管理系统 - 管理员管理函数
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 模态框工具函数
|
||||
* 多班级版班级管理系统 - 模态框工具函数
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 加减分管理函数
|
||||
* 多班级版班级管理系统 - 加减分管理函数
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 学生管理函数
|
||||
* 多班级版班级管理系统 - 学生管理函数
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
@@ -32,7 +32,7 @@
|
||||
const res = await apiPost('/api/admin/students', {
|
||||
student_no: studentNo,
|
||||
name: name,
|
||||
parent_phone: parentPhone,
|
||||
parent_account: parentPhone,
|
||||
dormitory_number: document.getElementById('addDormitoryNumber').value.trim()
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
|
||||
const res = await apiPut(`/api/admin/students/${studentId}`, {
|
||||
name: name,
|
||||
parent_phone: phone || null,
|
||||
parent_account: phone || null,
|
||||
dormitory_number: document.getElementById('editDormitoryNumber').value.trim()
|
||||
});
|
||||
|
||||
@@ -146,14 +146,14 @@
|
||||
const students = data.students || [];
|
||||
|
||||
let html = '<h4>预览数据</h4><div class="table-wrapper"><table><thead><tr>';
|
||||
html += '<th>学号</th><th>姓名</th><th>家长手机号</th><th>宿舍号</th><th>初始密码</th>';
|
||||
html += '<th>学号</th><th>姓名</th><th>家长账号(推荐手机号)</th><th>宿舍号</th><th>初始密码</th>';
|
||||
html += '</tr></thead><tbody>';
|
||||
|
||||
students.forEach(s => {
|
||||
html += `<tr>
|
||||
<td>${escapeHtml(s.student_no || '')}</td>
|
||||
<td>${escapeHtml(s.name || '')}</td>
|
||||
<td>${escapeHtml(s.parent_phone || '')}</td>
|
||||
<td>${escapeHtml(s.parent_account || '')}</td>
|
||||
<td>${escapeHtml(s.dormitory_number || '-')}</td>
|
||||
<td>${escapeHtml(s.password || '123456')}</td>
|
||||
</tr>`;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 科目管理函数
|
||||
* 多班级版班级管理系统 - 科目管理函数
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 通用工具函数
|
||||
* 多班级版班级管理系统 - 通用工具函数
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 家长端JS
|
||||
* 多班级版班级管理系统 - 家长端JS
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
59
frontend/assets/js/rankings.js
Normal file
59
frontend/assets/js/rankings.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 多班级版班级管理系统 - 排行榜JS
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
let currentType = 'conduct';
|
||||
|
||||
async function loadRankings(type) {
|
||||
const res = await apiGet('/api/admin/rankings', { type: type, limit: 50 });
|
||||
if (res && res.success && res.data) {
|
||||
const rankings = res.data.ranking || [];
|
||||
let html = '';
|
||||
if (rankings.length === 0) {
|
||||
html = '<tr><td colspan="4" style="text-align:center;">暂无排行数据</td></tr>';
|
||||
} else {
|
||||
rankings.forEach(function(item, index) {
|
||||
let rankClass = '';
|
||||
if (index === 0) rankClass = 'rank-gold';
|
||||
else if (index === 1) rankClass = 'rank-silver';
|
||||
else if (index === 2) rankClass = 'rank-bronze';
|
||||
|
||||
let pointsText = Number(item.points !== undefined ? item.points : (item.total_points || 0));
|
||||
if (pointsText > 0) {
|
||||
pointsText = '+' + pointsText;
|
||||
}
|
||||
|
||||
html += '<tr>' +
|
||||
'<td><span class="rank-badge ' + rankClass + '">' + (index + 1) + '</span></td>' +
|
||||
'<td>' + escapeHtml(item.student_no || '-') + '</td>' +
|
||||
'<td>' + escapeHtml(item.name || '-') + '</td>' +
|
||||
'<td><span class="record-points ' + (pointsText > 0 ? 'plus' : (pointsText < 0 ? 'minus' : '')) + '">' + pointsText + '</span></td>' +
|
||||
'</tr>';
|
||||
});
|
||||
}
|
||||
document.getElementById('rankingList').innerHTML = html;
|
||||
} else {
|
||||
document.getElementById('rankingList').innerHTML = '<tr><td colspan="4" style="text-align:center;">加载失败</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
window.switchTab = function(type, btn) {
|
||||
currentType = type;
|
||||
document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });
|
||||
btn.classList.add('active');
|
||||
loadRankings(type);
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadRankings(currentType);
|
||||
});
|
||||
|
||||
})();
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 学期管理页JS
|
||||
* 多班级版班级管理系统 - 学期管理页JS
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
@@ -41,7 +41,7 @@ async function loadSemesters() {
|
||||
const res = await apiGet('/api/semester/list');
|
||||
if (res && res.success) {
|
||||
let html = '';
|
||||
const semesters = res.data || [];
|
||||
const semesters = res.data.semesters || [];
|
||||
semesters.forEach(sem => {
|
||||
let statusText = '';
|
||||
let statusClass = '';
|
||||
@@ -252,7 +252,7 @@ async function viewArchiveData(semesterId, semesterName, page) {
|
||||
|
||||
if (res && res.success) {
|
||||
const data = res.data || {};
|
||||
const archives = data.archives || [];
|
||||
const archives = data.items || [];
|
||||
let html = '';
|
||||
archives.forEach(a => {
|
||||
html += `<tr>
|
||||
@@ -288,6 +288,80 @@ function renderArchivePagination(semesterId, semesterName) {
|
||||
});
|
||||
}
|
||||
|
||||
// ========== 周期重置功能 ==========
|
||||
|
||||
let pendingPeriodType = null;
|
||||
let periodArchivesType = null;
|
||||
let periodArchivesPage = 1;
|
||||
let periodArchivesTotalPages = 1;
|
||||
|
||||
function confirmPeriodReset(periodType) {
|
||||
pendingPeriodType = periodType;
|
||||
const label = periodType === 'weekly' ? '本周' : '本月';
|
||||
document.getElementById('periodResetText').innerHTML =
|
||||
`确定要执行 <strong>${label}重置</strong> 吗?<br>将保存当前所有学生的操行分快照,然后将所有学生操行分重置为初始值。`;
|
||||
document.getElementById('periodResetModal').style.display = 'flex';
|
||||
}
|
||||
|
||||
async function executePeriodReset() {
|
||||
if (!pendingPeriodType) return;
|
||||
|
||||
const res = await apiPost('/api/semester/period-reset', { period: pendingPeriodType });
|
||||
if (res && res.success) {
|
||||
showToast(res.message || '重置成功');
|
||||
closeModal('periodResetModal');
|
||||
pendingPeriodType = null;
|
||||
} else {
|
||||
showToast(res?.message || '重置失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function showPeriodArchives(type, page) {
|
||||
var periodType = type || periodArchivesType;
|
||||
page = page || 1;
|
||||
periodArchivesType = periodType;
|
||||
periodArchivesPage = page;
|
||||
|
||||
const label = periodType === 'weekly' ? '周' : '月';
|
||||
document.getElementById('periodArchivesTitle').textContent = label + '归档数据';
|
||||
|
||||
const res = await apiGet('/api/semester/period-archives', {
|
||||
period: periodType,
|
||||
page: page,
|
||||
page_size: 50
|
||||
});
|
||||
|
||||
if (res && res.success) {
|
||||
const data = res.data || {};
|
||||
const archives = data.items || [];
|
||||
let html = '';
|
||||
archives.forEach(function(a) {
|
||||
const resetByLabel = a.reset_by === 'auto' ? '自动' : '手动';
|
||||
html += '<tr>' +
|
||||
'<td>' + escapeHtml(a.period_label) + '</td>' +
|
||||
'<td>' + (a.rank_position || '-') + '</td>' +
|
||||
'<td>' + escapeHtml(a.student_no) + '</td>' +
|
||||
'<td>' + escapeHtml(a.student_name) + '</td>' +
|
||||
'<td>' + a.final_points + '</td>' +
|
||||
'<td>' + resetByLabel + '</td>' +
|
||||
'<td>' + formatDateTime(a.archived_at) + '</td>' +
|
||||
'</tr>';
|
||||
});
|
||||
if (archives.length === 0) {
|
||||
html = '<tr><td colspan="7" style="text-align:center;">暂无归档数据</td></tr>';
|
||||
}
|
||||
document.getElementById('periodArchivesList').innerHTML = html;
|
||||
|
||||
periodArchivesTotalPages = data.total_pages || 1;
|
||||
renderSmartPagination('periodArchivePagination', periodArchivesPage, periodArchivesTotalPages, function(p) {
|
||||
showPeriodArchives(periodArchivesType, p);
|
||||
});
|
||||
document.getElementById('periodArchivesModal').style.display = 'flex';
|
||||
} else {
|
||||
showToast(res?.message || '获取归档数据失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
loadSemesters();
|
||||
|
||||
window.fillSemesterDates = fillSemesterDates;
|
||||
@@ -302,5 +376,8 @@ window.confirmAssociate = confirmAssociate;
|
||||
window.showArchiveConfirm = showArchiveConfirm;
|
||||
window.confirmArchive = confirmArchive;
|
||||
window.viewArchiveData = viewArchiveData;
|
||||
window.confirmPeriodReset = confirmPeriodReset;
|
||||
window.executePeriodReset = executePeriodReset;
|
||||
window.showPeriodArchives = showPeriodArchives;
|
||||
|
||||
})();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 学生端作业情况JS
|
||||
* 多班级版班级管理系统 - 学生端作业情况JS
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 学生端JS
|
||||
* 多班级版班级管理系统 - 学生端JS
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 班级操行分管理系统 - 学生管理页JS
|
||||
* 多班级版班级管理系统 - 学生管理页JS
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
@@ -28,13 +28,13 @@ async function loadStudents(page = 1) {
|
||||
<td><a href="/admin/history.php?student_id=${student.student_id}" class="link">${escapeHtml(student.name)}</a></td>
|
||||
<td>${escapeHtml(student.dormitory_number || '-')}</td>
|
||||
<td>${student.total_points}</td>
|
||||
${userRole === '班主任' ? `<td>${student.parent_phone ? student.parent_phone.slice(0,3) + '******' + student.parent_phone.slice(-2) : '-'}</td>` : ''}
|
||||
${userRole === '班主任' ? `<td>${student.parent_account ? student.parent_account.slice(0,3) + '******' + student.parent_account.slice(-2) : '-'}</td>` : ''}
|
||||
<td>
|
||||
<div class="action-dropdown">
|
||||
<button class="btn btn-sm btn-outline" onclick="showSinglePointsModal(${student.student_id}, '${escapeHtml(student.name)}')">加减分</button>
|
||||
${userRole === '班主任' ? `<button class="btn btn-sm action-dropdown-toggle" onclick="toggleActionDropdown(this)">更多 ▼</button>
|
||||
<div class="action-dropdown-menu">
|
||||
<a onclick="showEditStudentModal(${student.student_id}, '${escapeHtml(student.student_no)}', '${escapeHtml(student.name)}', '${escapeHtml(student.parent_phone || '')}', '${escapeHtml(student.dormitory_number || '')}')">编辑</a>
|
||||
<a onclick="showEditStudentModal(${student.student_id}, '${escapeHtml(student.student_no)}', '${escapeHtml(student.name)}', '${escapeHtml(student.parent_account || '')}', '${escapeHtml(student.dormitory_number || '')}')">编辑</a>
|
||||
<a onclick="showResetStudentPasswordModal(${student.student_id}, '${escapeHtml(student.name)}')">重置密码</a>
|
||||
<a onclick="unlockStudent('${escapeHtml(student.student_no)}', '${escapeHtml(student.name)}')">解锁</a>
|
||||
<a class="danger" onclick="deleteStudent(${student.student_id}, '${escapeHtml(student.name)}')">删除</a>
|
||||
|
||||
@@ -1,49 +1,23 @@
|
||||
{
|
||||
"_comment1": "================================================",
|
||||
"_comment2": "班级操行分管理系统 - 学生批量导入模板",
|
||||
"_comment3": "开发者: Canglan | 版权: Sea Network Technology Studio",
|
||||
"_comment4": "================================================",
|
||||
"_comment5": "字段说明:",
|
||||
"_comment6": " student_no - 必填,学生学号,唯一标识",
|
||||
"_comment7": " name - 必填,学生姓名",
|
||||
"_comment8": " parent_phone - 可选,家长手机号(11位手机号)",
|
||||
"_comment9": " dormitory_number - 可选,宿舍号(支持字母数字组合,如 301-A)",
|
||||
"_comment10": " password - 可选,初始密码,不填则默认 123456",
|
||||
"_comment11": "================================================",
|
||||
"_comment12": "导入规则:",
|
||||
"_comment13": " 1. 学生操行分初始值 = 60分",
|
||||
"_comment14": " 2. 学生账号 = 学号,密码 = 指定的password或123456",
|
||||
"_comment15": " 3. 家长账号 = 手机号(若parent_phone有值),密码 = 指定的password或123456",
|
||||
"_comment16": " 4. 家长姓名默认显示为 '学生姓名家长'",
|
||||
"_comment17": "================================================",
|
||||
"students": [
|
||||
{
|
||||
"student_no": "20240001",
|
||||
"name": "张三",
|
||||
"parent_phone": "13800138001",
|
||||
"dormitory_number": "301-A",
|
||||
"password": "123456"
|
||||
},
|
||||
{
|
||||
"student_no": "20240002",
|
||||
"name": "李四",
|
||||
"parent_phone": "13800138002",
|
||||
"dormitory_number": "205",
|
||||
"password": "123456"
|
||||
},
|
||||
{
|
||||
"student_no": "20240003",
|
||||
"name": "王五",
|
||||
"parent_phone": "",
|
||||
"dormitory_number": "",
|
||||
"password": ""
|
||||
},
|
||||
{
|
||||
"student_no": "20240004",
|
||||
"name": "赵六",
|
||||
"parent_phone": "13800138004",
|
||||
"dormitory_number": "102-B",
|
||||
"password": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
"students": [
|
||||
{
|
||||
"student_no": "2025001",
|
||||
"name": "张三",
|
||||
"parent_account": "13800138001",
|
||||
"dormitory_number": "A301",
|
||||
"password": "123456"
|
||||
},
|
||||
{
|
||||
"student_no": "2025002",
|
||||
"name": "李四",
|
||||
"parent_account": "13800138002",
|
||||
"dormitory_number": "A302"
|
||||
},
|
||||
{
|
||||
"student_no": "2025003",
|
||||
"name": "王五",
|
||||
"parent_account": "",
|
||||
"dormitory_number": "B101"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 前端配置
|
||||
* 多班级版班级管理系统 - 前端配置
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
@@ -60,20 +60,38 @@ define('SITE_NAME', $config['SITE_NAME']);
|
||||
define('SESSION_TIMEOUT', (int)$config['SESSION_TIMEOUT']);
|
||||
define('ICP_ENABLED', $config['ICP_ENABLED'] !== 'false');
|
||||
define('ICP_NUMBER', $config['ICP_NUMBER'] ?? '');
|
||||
// 会话配置
|
||||
// 注意:此处不含 /api 前缀,前端 JS 会自动拼接 /api + path + /login
|
||||
define('SUPER_ADMIN_LOGIN_PATH', ($config['SUPER_ADMIN_LOGIN_PATH'] ?? '/super-admin'));
|
||||
// 会话配置
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
ini_set('session.use_only_cookies', 1);
|
||||
ini_set('session.cookie_secure', 1);
|
||||
// 开发环境允许 HTTP,生产环境强制 HTTPS
|
||||
$appEnv = $config['APP_ENV'] ?? 'production';
|
||||
ini_set('session.cookie_secure', $appEnv === 'production' ? 1 : 0);
|
||||
ini_set('session.cookie_samesite', 'Lax');
|
||||
ini_set('session.gc_maxlifetime', 7200);
|
||||
ini_set('session.gc_maxlifetime', SESSION_TIMEOUT);
|
||||
session_name('CLASS_SESSION');
|
||||
|
||||
session_start();
|
||||
|
||||
// 应用层会话超时检查
|
||||
if (isset($_SESSION['login_time']) && (time() - $_SESSION['login_time'] > SESSION_TIMEOUT)) {
|
||||
session_unset();
|
||||
session_destroy();
|
||||
if (strpos($_SERVER['REQUEST_URI'] ?? '', '/api/') === false) {
|
||||
header('Location: /index.php');
|
||||
exit();
|
||||
}
|
||||
http_response_code(401);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(['success' => false, 'message' => '会话已过期,请重新登录']);
|
||||
exit();
|
||||
}
|
||||
|
||||
// 时区设置
|
||||
date_default_timezone_set('Asia/Shanghai');
|
||||
|
||||
// 生产环境关闭错误显示
|
||||
error_reporting(0);
|
||||
ini_set('display_errors', 0);
|
||||
// 生产环境关闭错误显示(保留错误日志记录,仅隐藏页面输出)
|
||||
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
@@ -1,17 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 公共底部
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
?>
|
||||
<div class="footer">
|
||||
<p>© <?php echo date('Y'); ?> Sea Network Technology Studio</p>
|
||||
</div>
|
||||
</div><!-- /.container —— 依赖页面包含 <div class="container"> -->
|
||||
<footer class="footer">
|
||||
<p>© <?php echo date('Y'); ?> <?php echo htmlspecialchars(SITE_NAME, ENT_QUOTES, 'UTF-8'); ?> · <span class="footer-dev">Powered by Canglan / Sea Network Technology Studio</span></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 公共头部
|
||||
* 多班级版班级管理系统 - 公共头部
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
@@ -17,6 +17,8 @@ if (!isset($_SESSION)) {
|
||||
$current_page = basename($_SERVER['PHP_SELF'], '.php');
|
||||
$user_type = $_SESSION['user_type'] ?? '';
|
||||
$role = $_SESSION['role'] ?? '';
|
||||
$class_id = $_SESSION['class_id'] ?? null;
|
||||
$class_name = $_SESSION['class_name'] ?? '';
|
||||
$page_title = $page_title ?? '首页';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
@@ -24,7 +26,7 @@ $page_title = $page_title ?? '首页';
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title><?php echo SITE_NAME; ?> - <?php echo $page_title; ?></title>
|
||||
<title><?php echo htmlspecialchars(SITE_NAME, ENT_QUOTES, 'UTF-8'); ?> - <?php echo htmlspecialchars($page_title); ?></title>
|
||||
<link rel="stylesheet" href="/assets/css/style.css">
|
||||
<?php if ($user_type === 'admin'): ?>
|
||||
<link rel="stylesheet" href="/assets/css/admin.css">
|
||||
@@ -32,8 +34,11 @@ $page_title = $page_title ?? '首页';
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1><?php echo SITE_NAME; ?></h1>
|
||||
<h1><?php echo htmlspecialchars(SITE_NAME, ENT_QUOTES, 'UTF-8'); ?></h1>
|
||||
<div class="header-info">
|
||||
<?php if ($class_name): ?>
|
||||
<span class="class-name" id="className"><?php echo htmlspecialchars($class_name); ?></span>
|
||||
<?php endif; ?>
|
||||
<span class="user-name" id="userName"><?php echo htmlspecialchars($_SESSION['real_name'] ?? ''); ?></span>
|
||||
<?php if ($role): ?>
|
||||
<span class="user-role">(<?php echo htmlspecialchars($role); ?>)</span>
|
||||
@@ -42,38 +47,46 @@ $page_title = $page_title ?? '首页';
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
window.API_BASE_URL = '<?php echo API_BASE_URL; ?>';
|
||||
window.JWT_STORAGE_KEY = '<?php echo JWT_STORAGE_KEY; ?>';
|
||||
window.USER_STORAGE_KEY = '<?php echo USER_STORAGE_KEY; ?>';
|
||||
window.API_BASE_URL = <?php echo json_encode(API_BASE_URL); ?>;
|
||||
window.JWT_STORAGE_KEY = <?php echo json_encode(JWT_STORAGE_KEY); ?>;
|
||||
window.USER_STORAGE_KEY = <?php echo json_encode(USER_STORAGE_KEY); ?>;
|
||||
window.CLASS_ID = <?php echo $class_id ? $class_id : 'null'; ?>;
|
||||
window.CLASS_NAME = <?php echo json_encode($class_name); ?>;
|
||||
</script>
|
||||
<script>
|
||||
// 从后端API同步加载扣分规则配置
|
||||
// 从后端API异步加载扣分规则配置(优先加载班级级配置)
|
||||
(function() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', window.API_BASE_URL + '/api/config/deduction-rules', false); // 同步请求
|
||||
try {
|
||||
xhr.send();
|
||||
if (xhr.status === 200) {
|
||||
var resp = JSON.parse(xhr.responseText);
|
||||
if (resp.success && resp.data) {
|
||||
window.DEDUCTION_HOMEWORK_NOT_SUBMIT = resp.data.DEDUCTION_HOMEWORK_NOT_SUBMIT;
|
||||
window.DEDUCTION_HOMEWORK_LATE = resp.data.DEDUCTION_HOMEWORK_LATE;
|
||||
window.DEDUCTION_ATTENDANCE_ABSENT = resp.data.DEDUCTION_ATTENDANCE_ABSENT;
|
||||
window.DEDUCTION_ATTENDANCE_LATE = resp.data.DEDUCTION_ATTENDANCE_LATE;
|
||||
window.DEDUCTION_ATTENDANCE_LEAVE = resp.data.DEDUCTION_ATTENDANCE_LEAVE;
|
||||
window.STUDENT_INITIAL_POINTS = resp.data.STUDENT_INITIAL_POINTS;
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
// API加载失败时使用默认值
|
||||
window.DEDUCTION_HOMEWORK_NOT_SUBMIT = 2;
|
||||
window.DEDUCTION_HOMEWORK_LATE = 1;
|
||||
window.DEDUCTION_ATTENDANCE_ABSENT = 3;
|
||||
window.DEDUCTION_ATTENDANCE_LATE = 1;
|
||||
window.DEDUCTION_ATTENDANCE_LEAVE = 0;
|
||||
window.STUDENT_INITIAL_POINTS = 60;
|
||||
// 先设置默认值,避免异步加载期间其他脚本读取到 undefined
|
||||
window.DEDUCTION_HOMEWORK_NOT_SUBMIT = 2;
|
||||
window.DEDUCTION_HOMEWORK_LATE = 1;
|
||||
window.DEDUCTION_ATTENDANCE_ABSENT = 3;
|
||||
window.DEDUCTION_ATTENDANCE_LATE = 1;
|
||||
window.DEDUCTION_ATTENDANCE_LEAVE = 0;
|
||||
window.STUDENT_INITIAL_POINTS = 60;
|
||||
|
||||
var token = localStorage.getItem(window.JWT_STORAGE_KEY) || '';
|
||||
var apiUrl = window.API_BASE_URL + '/api/config/deduction-rules';
|
||||
if (window.CLASS_ID) {
|
||||
apiUrl += '?class_id=' + window.CLASS_ID;
|
||||
}
|
||||
fetch(apiUrl, {
|
||||
headers: token ? { 'Authorization': 'Bearer ' + token } : {}
|
||||
}).then(function(resp) {
|
||||
return resp.json();
|
||||
}).then(function(data) {
|
||||
if (data.success && data.data) {
|
||||
window.DEDUCTION_HOMEWORK_NOT_SUBMIT = data.data.DEDUCTION_HOMEWORK_NOT_SUBMIT;
|
||||
window.DEDUCTION_HOMEWORK_LATE = data.data.DEDUCTION_HOMEWORK_LATE;
|
||||
window.DEDUCTION_ATTENDANCE_ABSENT = data.data.DEDUCTION_ATTENDANCE_ABSENT;
|
||||
window.DEDUCTION_ATTENDANCE_LATE = data.data.DEDUCTION_ATTENDANCE_LATE;
|
||||
window.DEDUCTION_ATTENDANCE_LEAVE = data.data.DEDUCTION_ATTENDANCE_LEAVE;
|
||||
window.STUDENT_INITIAL_POINTS = data.data.STUDENT_INITIAL_POINTS;
|
||||
}
|
||||
}).catch(function() {
|
||||
// API加载失败时保留默认值
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script src="/assets/js/common.js"></script>
|
||||
<script src="/assets/js/modules/utils.js"></script>
|
||||
<script src="/assets/js/modules/utils.js"></script>
|
||||
<div class="container">
|
||||
@@ -1,20 +1,30 @@
|
||||
<div class="nav">
|
||||
<a href="/admin/dashboard.php" class="nav-item<?php echo $current_page === 'dashboard' ? ' active' : ''; ?>">首页</a>
|
||||
<?php if ($user_type === 'super_admin'): ?>
|
||||
<a href="/admin/classes.php" class="nav-item<?php echo $current_page === 'classes' ? ' active' : ''; ?>">班级管理</a>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array($role, ['班主任', '系统管理员'])): ?>
|
||||
<a href="/admin/students.php" class="nav-item<?php echo $current_page === 'students' ? ' active' : ''; ?>">学生管理</a>
|
||||
<?php if ($role === '班主任' || $role === '班长' || $role === '学习委员' || $role === '考勤委员' || $role === '劳动委员' || $role === '志愿委员'): ?>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array($role, ['班主任', '班长', '学习委员', '考勤委员', '劳动委员', '志愿委员', '科任老师', '系统管理员'])): ?>
|
||||
<a href="/admin/conduct.php" class="nav-item<?php echo $current_page === 'conduct' ? ' active' : ''; ?>">操行分管理</a>
|
||||
<?php endif; ?>
|
||||
<?php if ($role === '班主任' || $role === '学习委员'): ?>
|
||||
<?php if (in_array($role, ['班主任', '学习委员', '科任老师', '系统管理员'])): ?>
|
||||
<a href="/admin/homework.php" class="nav-item<?php echo $current_page === 'homework' ? ' active' : ''; ?>">作业扣分</a>
|
||||
<?php endif; ?>
|
||||
<?php if ($role === '班主任' || $role === '考勤委员'): ?>
|
||||
<?php if ($role === '课代表'): ?>
|
||||
<a href="/admin/cadre_homework.php" class="nav-item<?php echo $current_page === 'cadre_homework' ? ' active' : ''; ?>">作业管理</a>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array($role, ['班主任', '考勤委员', '系统管理员'])): ?>
|
||||
<a href="/admin/attendance.php" class="nav-item<?php echo $current_page === 'attendance' ? ' active' : ''; ?>">考勤管理</a>
|
||||
<?php endif; ?>
|
||||
<?php if ($role === '班主任'): ?>
|
||||
<?php if (in_array($role, ['班主任', '系统管理员'])): ?>
|
||||
<a href="/admin/admins.php" class="nav-item<?php echo $current_page === 'admins' ? ' active' : ''; ?>">管理员管理</a>
|
||||
<?php endif; ?>
|
||||
<?php if ($role === '班主任'): ?>
|
||||
<a href="/admin/semesters.php" class="nav-item<?php echo $current_page === 'semesters' ? ' active' : ''; ?>">学期管理</a>
|
||||
<a href="/admin/class_settings.php" class="nav-item<?php echo $current_page === 'class_settings' ? ' active' : ''; ?>">班级设置</a>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array($role, ['班主任', '系统管理员'])): ?>
|
||||
<a href="/admin/rankings.php" class="nav-item<?php echo $current_page === 'rankings' ? ' active' : ''; ?>">排行榜</a>
|
||||
<?php endif; ?>
|
||||
<a href="/admin/history.php" class="nav-item<?php echo $current_page === 'history' ? ' active' : ''; ?>">历史记录</a>
|
||||
<a href="/admin/password.php" class="nav-item<?php echo $current_page === 'password' ? ' active' : ''; ?>">修改密码</a>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 登录入口
|
||||
* 多班级版班级管理系统 - 登录入口
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
@@ -16,7 +16,8 @@ if (isset($_SESSION['user_id']) && isset($_SESSION['user_type'])) {
|
||||
$redirect = [
|
||||
'student' => '/student/dashboard.php',
|
||||
'parent' => '/parent/dashboard.php',
|
||||
'admin' => '/admin/dashboard.php'
|
||||
'admin' => '/admin/dashboard.php',
|
||||
'super_admin' => '/admin/dashboard.php'
|
||||
];
|
||||
header("Location: " . ($redirect[$_SESSION['user_type']] ?? '/index.php'));
|
||||
exit();
|
||||
@@ -27,14 +28,14 @@ if (isset($_SESSION['user_id']) && isset($_SESSION['user_type'])) {
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title><?php echo SITE_NAME; ?> - 登录</title>
|
||||
<title><?php echo htmlspecialchars(SITE_NAME, ENT_QUOTES, 'UTF-8'); ?> - 登录</title>
|
||||
<link rel="stylesheet" href="/assets/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<div class="login-header">
|
||||
<h1><?php echo SITE_NAME; ?></h1>
|
||||
<p>学生 / 家长 / 管理端 统一登录</p>
|
||||
<h1><?php echo htmlspecialchars(SITE_NAME, ENT_QUOTES, 'UTF-8'); ?></h1>
|
||||
<p>学生 / 家长 / 管理员 统一登录</p>
|
||||
</div>
|
||||
|
||||
<form id="loginForm" class="login-form">
|
||||
@@ -59,9 +60,9 @@ if (isset($_SESSION['user_id']) && isset($_SESSION['user_type'])) {
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.API_BASE_URL = '<?php echo API_BASE_URL; ?>';
|
||||
window.JWT_STORAGE_KEY = '<?php echo JWT_STORAGE_KEY; ?>';
|
||||
window.USER_STORAGE_KEY = '<?php echo USER_STORAGE_KEY; ?>';
|
||||
window.API_BASE_URL = <?php echo json_encode(API_BASE_URL); ?>;
|
||||
window.JWT_STORAGE_KEY = <?php echo json_encode(JWT_STORAGE_KEY); ?>;
|
||||
window.USER_STORAGE_KEY = <?php echo json_encode(USER_STORAGE_KEY); ?>;
|
||||
|
||||
document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
@@ -105,7 +106,9 @@ if (isset($_SESSION['user_id']) && isset($_SESSION['user_type'])) {
|
||||
username: userData.username,
|
||||
real_name: userData.real_name,
|
||||
role: userData.role || '',
|
||||
student_id: userData.student_id || null
|
||||
student_id: userData.student_id || null,
|
||||
class_id: userData.class_id || null,
|
||||
class_name: userData.class_name || ''
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 家长端考勤记录
|
||||
* 多班级版班级管理系统 - 家长端考勤记录
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
@@ -25,6 +25,7 @@ include __DIR__ . '/../includes/header.php';
|
||||
<a href="/parent/dashboard.php" class="nav-item">首页</a>
|
||||
<a href="/parent/history.php" class="nav-item">历史记录</a>
|
||||
<a href="/parent/attendance.php" class="nav-item active">考勤记录</a>
|
||||
<a href="/parent/password.php" class="nav-item">修改密码</a>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 家长端首页
|
||||
* 多班级版班级管理系统 - 家长端首页
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
@@ -25,6 +25,7 @@ include __DIR__ . '/../includes/header.php';
|
||||
<a href="/parent/dashboard.php" class="nav-item active">首页</a>
|
||||
<a href="/parent/history.php" class="nav-item">历史记录</a>
|
||||
<a href="/parent/attendance.php" class="nav-item">考勤记录</a>
|
||||
<a href="/parent/password.php" class="nav-item">修改密码</a>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 家长端历史记录
|
||||
* 多班级版班级管理系统 - 家长端历史记录
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
@@ -25,6 +25,7 @@ include __DIR__ . '/../includes/header.php';
|
||||
<a href="/parent/dashboard.php" class="nav-item">首页</a>
|
||||
<a href="/parent/history.php" class="nav-item active">历史记录</a>
|
||||
<a href="/parent/attendance.php" class="nav-item">考勤记录</a>
|
||||
<a href="/parent/password.php" class="nav-item">修改密码</a>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
106
frontend/parent/password.php
Normal file
106
frontend/parent/password.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?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']) || $_SESSION['user_type'] !== 'parent') {
|
||||
header('Location: /index.php');
|
||||
exit();
|
||||
}
|
||||
|
||||
include __DIR__ . '/../includes/header.php';
|
||||
?>
|
||||
|
||||
<div class="nav">
|
||||
<a href="/parent/dashboard.php" class="nav-item">首页</a>
|
||||
<a href="/parent/history.php" class="nav-item">历史记录</a>
|
||||
<a href="/parent/attendance.php" class="nav-item">考勤记录</a>
|
||||
<a href="/parent/password.php" class="nav-item active">修改密码</a>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<div class="card-title">修改密码</div>
|
||||
<div id="featureDisabled" style="display:none;">
|
||||
<p style="text-align:center;color:#999;padding:30px 0;">该功能暂未开放,请联系班主任启用"家长改密"功能开关。</p>
|
||||
</div>
|
||||
<form id="passwordForm" style="display:none;">
|
||||
<div class="form-group">
|
||||
<label>原密码 <span style="color:red;">*</span></label>
|
||||
<input type="password" id="oldPassword" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>新密码 <span style="color:red;">*</span></label>
|
||||
<input type="password" id="newPassword" required>
|
||||
<small>密码长度6-20位,需包含大写字母、小写字母、数字、特殊符号中的至少3种</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>确认新密码 <span style="color:red;">*</span></label>
|
||||
<input type="password" id="confirmPassword" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">确认修改</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function checkFeature() {
|
||||
var res = await apiGet('/api/class/features');
|
||||
if (res && res.success && res.data && res.data.features) {
|
||||
var val = res.data.features.parent_password_change_enabled;
|
||||
var enabled = val === 1 || val === '1' || val === true;
|
||||
if (enabled) {
|
||||
document.getElementById('passwordForm').style.display = '';
|
||||
} else {
|
||||
document.getElementById('featureDisabled').style.display = '';
|
||||
}
|
||||
} else {
|
||||
document.getElementById('featureDisabled').style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('passwordForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
var oldPassword = document.getElementById('oldPassword').value;
|
||||
var newPassword = document.getElementById('newPassword').value;
|
||||
var confirmPassword = document.getElementById('confirmPassword').value;
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
showToast('两次输入的新密码不一致', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword.length < 6 || newPassword.length > 20) {
|
||||
showToast('密码长度需为6-20位', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
var res = await apiPost('/api/parent/password', {
|
||||
old_password: oldPassword,
|
||||
new_password: newPassword
|
||||
});
|
||||
|
||||
if (res && res.success) {
|
||||
showToast('密码修改成功,请重新登录');
|
||||
setTimeout(function() { logout(); }, 1500);
|
||||
} else {
|
||||
showToast(res && res.message ? res.message : '密码修改失败', 'error');
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', checkFeature);
|
||||
</script>
|
||||
<script src="/assets/js/parent.js"></script>
|
||||
|
||||
<?php include __DIR__ . '/../includes/footer.php'; ?>
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 学生端考勤记录
|
||||
* 多班级版班级管理系统 - 学生端考勤记录
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 学生端主页
|
||||
* 多班级版班级管理系统 - 学生端主页
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
@@ -237,7 +237,7 @@ include __DIR__ . '/../includes/header.php';
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const STUDENT_ID = <?php echo $student_id; ?>;
|
||||
const STUDENT_ID = <?php echo intval($student_id); ?>;
|
||||
|
||||
let conductPage = 1;
|
||||
let conductTotalPages = 1;
|
||||
@@ -302,8 +302,8 @@ include __DIR__ . '/../includes/header.php';
|
||||
|
||||
// 获取个人信息(宿舍号)
|
||||
const infoRes = await apiGet('/api/student/my-info');
|
||||
if (infoRes && infoRes.success && infoRes.data.dormitory_number) {
|
||||
document.getElementById('dormitoryNumber').textContent = infoRes.data.dormitory_number;
|
||||
if (infoRes && infoRes.success && infoRes.data.student && infoRes.data.student.dormitory_number) {
|
||||
document.getElementById('dormitoryNumber').textContent = infoRes.data.student.dormitory_number;
|
||||
document.getElementById('dormitoryInfo').style.display = '';
|
||||
}
|
||||
|
||||
@@ -311,9 +311,9 @@ include __DIR__ . '/../includes/header.php';
|
||||
const rankingRes = await apiGet('/api/student/ranking', { limit: 100 });
|
||||
if (rankingRes && rankingRes.success) {
|
||||
const ranking = rankingRes.data.ranking || [];
|
||||
const rank = ranking.find(s => s.student_id === parseInt(STUDENT_ID));
|
||||
if (rank) {
|
||||
document.getElementById('studentRank').textContent = `第${rank.rank}名`;
|
||||
const rankIndex = ranking.findIndex(s => s.student_id === parseInt(STUDENT_ID));
|
||||
if (rankIndex >= 0) {
|
||||
document.getElementById('studentRank').textContent = `第${rankIndex + 1}名`;
|
||||
} else {
|
||||
document.getElementById('studentRank').textContent = '--';
|
||||
}
|
||||
@@ -321,9 +321,8 @@ include __DIR__ . '/../includes/header.php';
|
||||
// 获取作业扣分统计
|
||||
const homeworkRes = await apiGet(`/api/student/homework/${STUDENT_ID}`);
|
||||
if (homeworkRes && homeworkRes.success) {
|
||||
const stats = homeworkRes.data.statistics;
|
||||
const deductions = stats.deductions || 0;
|
||||
const total = stats.total || 0;
|
||||
const homework = homeworkRes.data.homework || [];
|
||||
const deductions = homework.length;
|
||||
document.getElementById('homeworkRate').textContent = `${deductions} 次扣分`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 学生端作业情况
|
||||
* 多班级版班级管理系统 - 学生端作业情况
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 学生端修改密码
|
||||
* 多班级版班级管理系统 - 学生端修改密码
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* 班级操行分管理系统 - 学生端学期记录页面
|
||||
* 多班级版班级管理系统 - 学生端学期记录页面
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: MIT License
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
134
frontend/super-admin/login.php
Normal file
134
frontend/super-admin/login.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* 多班级版班级管理系统 - 超级管理员登录页
|
||||
*
|
||||
* 开发者: Canglan
|
||||
* 联系方式: admin@sea-studio.top
|
||||
* 版权归属: Sea Network Technology Studio
|
||||
* 许可证: Apache License 2.0
|
||||
*
|
||||
* 版权所有 © Sea Network Technology Studio
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config.php';
|
||||
|
||||
if (isset($_SESSION['user_id']) && $_SESSION['user_type'] === 'super_admin') {
|
||||
header('Location: /admin/classes.php');
|
||||
exit();
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
<title><?php echo htmlspecialchars(SITE_NAME, ENT_QUOTES, 'UTF-8'); ?> - 系统管理员登录</title>
|
||||
<link rel="stylesheet" href="/assets/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<div class="login-header">
|
||||
<h1><?php echo htmlspecialchars(SITE_NAME, ENT_QUOTES, 'UTF-8'); ?></h1>
|
||||
<p>系统管理员登录</p>
|
||||
</div>
|
||||
|
||||
<form id="superAdminLoginForm" class="login-form">
|
||||
<div class="form-group">
|
||||
<label>用户名</label>
|
||||
<input type="text" id="username" name="username" required autocomplete="off" placeholder="系统管理员账号">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>密码</label>
|
||||
<input type="password" id="password" name="password" required placeholder="请输入密码">
|
||||
</div>
|
||||
<button type="submit" class="btn-login">登 录</button>
|
||||
<div id="errorMsg" class="error-msg" style="display: none;"></div>
|
||||
</form>
|
||||
|
||||
<div class="login-footer">
|
||||
<p>© <?php echo date('Y'); ?> Sea Network Technology Studio</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.API_BASE_URL = <?php echo json_encode(API_BASE_URL); ?>;
|
||||
window.JWT_STORAGE_KEY = <?php echo json_encode(JWT_STORAGE_KEY); ?>;
|
||||
window.USER_STORAGE_KEY = <?php echo json_encode(USER_STORAGE_KEY); ?>;
|
||||
|
||||
const superAdminLoginPath = '<?php echo htmlspecialchars(SUPER_ADMIN_LOGIN_PATH, ENT_QUOTES, 'UTF-8'); ?>';
|
||||
|
||||
document.getElementById('superAdminLoginForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const username = document.getElementById('username').value.trim();
|
||||
const password = document.getElementById('password').value;
|
||||
const errorMsg = document.getElementById('errorMsg');
|
||||
|
||||
if (!username || !password) {
|
||||
showError('请填写用户名和密码');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const loginUrl = API_BASE_URL + '/api' + superAdminLoginPath + '/login';
|
||||
const response = await fetch(loginUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username: username, password: password })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.data) {
|
||||
const userData = data.data;
|
||||
|
||||
localStorage.setItem(JWT_STORAGE_KEY, userData.token);
|
||||
localStorage.setItem(USER_STORAGE_KEY, JSON.stringify(userData));
|
||||
|
||||
try {
|
||||
const sessionResponse = await fetch('/api/save_session.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + userData.token
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_id: userData.user_id,
|
||||
user_type: 'super_admin',
|
||||
username: userData.username,
|
||||
real_name: userData.real_name || '',
|
||||
role: '系统管理员',
|
||||
class_id: null,
|
||||
class_name: '',
|
||||
need_change_password: userData.need_change_password || false
|
||||
})
|
||||
});
|
||||
|
||||
if (!sessionResponse.ok) {
|
||||
console.warn('Session 同步失败,但继续跳转');
|
||||
}
|
||||
} catch (sessionError) {
|
||||
console.warn('Session 同步异常:', sessionError);
|
||||
}
|
||||
|
||||
window.location.href = userData.redirect || '/admin/classes.php';
|
||||
} else {
|
||||
showError(data.message || '登录失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录错误:', error);
|
||||
showError('网络错误,请检查后端服务是否启动');
|
||||
}
|
||||
});
|
||||
|
||||
function showError(msg) {
|
||||
const errorMsg = document.getElementById('errorMsg');
|
||||
errorMsg.textContent = msg;
|
||||
errorMsg.style.display = 'block';
|
||||
setTimeout(() => {
|
||||
errorMsg.style.display = 'none';
|
||||
}, 3000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user