Files
SharedClassManager/frontend/admin/attendance.php
2026-04-14 11:35:56 +08:00

250 lines
8.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 班级操行分管理系统 - 管理端考勤管理
*
* 开发者: Canglan
* 联系方式: admin@sea-studio.top
* 版权归属: Sea Network Technology Studio
* 许可证: MIT License
*
* 版权所有 © Sea Network Technology Studio
*/
require_once __DIR__ . '/../config.php';
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'admin') {
header('Location: /index.php');
exit();
}
$page_title = '考勤管理';
$role = $_SESSION['role'] ?? '';
if (!in_array($role, ['班主任', '考勤委员'])) {
header('Location: /admin/dashboard.php');
exit();
}
include __DIR__ . '/../includes/header.php';
?>
<?php include __DIR__ . '/../includes/nav.php'; ?>
<div class="container">
<!-- 考勤操作工具栏 -->
<div class="card">
<div class="attendance-toolbar">
<div class="form-group" style="margin:0">
<label>日期</label>
<input type="date" id="attendanceDate" value="<?php echo date('Y-m-d'); ?>">
</div>
<div class="status-group">
<button class="status-btn active" data-status="absent" onclick="selectStatus(this)">缺勤(-<span class="att-absent"></span>分)</button>
<button class="status-btn" data-status="late" onclick="selectStatus(this)">迟到(-<span class="att-late"></span>分)</button>
<button class="status-btn" data-status="leave" onclick="selectStatus(this)">请假(-<span class="att-leave"></span>分)</button>
</div>
<input type="text" id="attendanceReason" placeholder="原因(可选)" style="flex:1;min-width:150px;">
<button class="btn btn-primary" onclick="selectAllStudents()">全选</button>
<button class="btn" onclick="deselectAllStudents()">取消全选</button>
<button class="btn btn-danger" onclick="submitAttendance()">提交考勤</button>
<button class="btn btn-secondary" onclick="loadAttendanceRecords()">查询记录</button>
</div>
</div>
<!-- 学生方格网格 -->
<div class="card">
<div class="card-title">点击选择有考勤异常的学生</div>
<div class="student-grid" id="studentGrid">
<!-- JS 动态生成 -->
</div>
</div>
<!-- 历史考勤记录 -->
<div class="card">
<div class="card-title">考勤记录</div>
<div class="table-wrapper">
<table class="table">
<thead>
<tr><th>学号</th><th>姓名</th><th>状态</th><th>原因</th><th>记录人</th><th>扣分</th></tr>
</thead>
<tbody id="attendanceList"></tbody>
</table>
</div>
</div>
</div>
<script>
let currentStatus = 'absent';
let studentsData = [];
let existingRecords = [];
// 初始化考勤扣分配置
const attAbsent = window.DEDUCTION_ATTENDANCE_ABSENT || 5;
const attLate = window.DEDUCTION_ATTENDANCE_LATE || 2;
const attLeave = window.DEDUCTION_ATTENDANCE_LEAVE || 1;
// 更新页面中的配置值显示
document.querySelectorAll('.att-absent').forEach(el => el.textContent = attAbsent);
document.querySelectorAll('.att-late').forEach(el => el.textContent = attLate);
document.querySelectorAll('.att-leave').forEach(el => el.textContent = attLeave);
// 选择考勤状态
function selectStatus(btn) {
document.querySelectorAll('.status-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentStatus = btn.dataset.status;
}
// 加载学生列表
async function loadStudents() {
const res = await apiGet('/api/admin/students');
if (res && res.success) {
studentsData = res.data.students;
renderStudentGrid();
// 同时加载当天已有考勤记录
await loadExistingRecords();
} else {
document.getElementById('studentGrid').innerHTML = '<div style="text-align:center;padding:20px;color:#999;">加载学生列表失败</div>';
}
}
// 渲染学生方格
function renderStudentGrid() {
let html = '';
studentsData.forEach(student => {
const hasRecord = existingRecords.some(r => r.student_id === student.student_id);
html += `<div class="student-cell${hasRecord ? ' has-record' : ''}"
data-id="${student.student_id}"
data-name="${escapeHtml(student.name)}"
onclick="toggleStudent(this)">
${escapeHtml(student.name)}
</div>`;
});
if (studentsData.length === 0) {
html = '<div style="text-align:center;padding:20px;color:#999;width:100%;">暂无学生数据</div>';
}
document.getElementById('studentGrid').innerHTML = html;
}
// 切换学生选中状态
function toggleStudent(cell) {
cell.classList.toggle('selected');
}
// 全选
function selectAllStudents() {
document.querySelectorAll('.student-cell:not(.has-record)').forEach(cell => {
cell.classList.add('selected');
});
}
// 取消全选
function deselectAllStudents() {
document.querySelectorAll('.student-cell').forEach(cell => {
cell.classList.remove('selected');
});
}
// 加载当天已有考勤记录(用于标记 .has-record
async function loadExistingRecords() {
const date = document.getElementById('attendanceDate').value;
const res = await apiGet('/api/admin/attendance/records', { date });
if (res && res.success) {
existingRecords = res.data.records || [];
renderStudentGrid(); // 重新渲染以标记 has-record
}
}
// 提交考勤
async function submitAttendance() {
const selectedCells = document.querySelectorAll('.student-cell.selected');
if (selectedCells.length === 0) {
showToast('请先选择有考勤异常的学生', 'warning');
return;
}
const date = document.getElementById('attendanceDate').value;
const reason = document.getElementById('attendanceReason').value;
// 检查是否有已存在记录的学生
const hasRecordStudents = [];
selectedCells.forEach(cell => {
if (cell.classList.contains('has-record')) {
hasRecordStudents.push(cell.dataset.name);
}
});
if (hasRecordStudents.length > 0) {
const confirmed = confirm(`以下学生已有考勤记录:${hasRecordStudents.join('、')},是否继续提交?`);
if (!confirmed) return;
}
// 批量提交
const promises = [];
selectedCells.forEach(cell => {
const studentId = parseInt(cell.dataset.id);
promises.push(
apiPost('/api/admin/attendance', {
student_id: studentId,
date: date,
status: currentStatus,
reason: reason,
apply_deduction: true
})
);
});
const results = await Promise.allSettled(promises);
const succeeded = results.filter(r => r.status === 'fulfilled' && r.value?.success).length;
const failed = results.length - succeeded;
if (failed === 0) {
showToast(`考勤提交成功(${succeeded}条)`);
} else {
showToast(`提交完成:成功${succeeded}条,失败${failed}条`, 'error');
}
// 刷新
deselectAllStudents();
await loadExistingRecords();
loadAttendanceRecords();
}
// 查询并渲染考勤记录
async function loadAttendanceRecords() {
const date = document.getElementById('attendanceDate').value;
const res = await apiGet('/api/admin/attendance/records', { date });
if (res && res.success) {
let html = '';
const records = res.data.records || [];
records.forEach(record => {
html += `<tr>
<td>${escapeHtml(record.student_no)}</td>
<td>${escapeHtml(record.student_name)}</td>
<td>${getStatusBadge(record.status, 'attendance')}</td>
<td>${escapeHtml(record.reason || '-')}</td>
<td>${escapeHtml(record.recorder_name || '-')}</td>
<td>${record.deduction_applied ? '已扣分' : '-'}</td>
</tr>`;
});
if (records.length === 0) {
html = '<tr><td colspan="6" style="text-align:center;">暂无考勤记录</td></tr>';
}
document.getElementById('attendanceList').innerHTML = html;
}
}
// 日期变化时重新加载
document.getElementById('attendanceDate').addEventListener('change', function() {
loadExistingRecords();
loadAttendanceRecords();
});
// 页面初始化
loadStudents();
loadAttendanceRecords();
</script>
<script src="/assets/js/admin.js"></script>
<?php include __DIR__ . '/../includes/footer.php'; ?>