Files
SharedClassManager/frontend/admin/attendance.php

283 lines
10 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="form-group" style="margin:0">
<label>时段</label>
<select id="attendanceSlot">
<option value="morning">早上 7:15</option>
<option value="afternoon">中午 14:00</option>
<option value="evening">晚修 19:30</option>
</select>
</div>
<div class="status-group">
<button class="status-btn active" data-status="absent" onclick="selectStatus(this)" id="btnAbsent">缺勤</button>
<button class="status-btn" data-status="late" onclick="selectStatus(this)" id="btnLate">迟到</button>
<button class="status-btn" data-status="leave" onclick="selectStatus(this)" id="btnLeave">请假</button>
<input type="number" id="customDeduction" placeholder="自定义扣分" min="0" max="20" style="width:100px;margin-left:10px;" title="留空或0使用默认值">
</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>
<style>
.student-cell { display: flex; flex-direction: column; align-items: center; }
.student-cell-name { font-size: 14px; font-weight: 500; }
.student-cell-no { font-size: 11px; color: #999; }
</style>
<script>
let currentStatus = 'absent';
let studentsData = [];
let existingRecords = [];
// 考勤扣分配置映射(从后端配置注入)
const attendanceDeductionMap = {
absent: window.DEDUCTION_ATTENDANCE_ABSENT || 3,
late: window.DEDUCTION_ATTENDANCE_LATE || 1,
leave: window.DEDUCTION_ATTENDANCE_LEAVE || 0
};
// 初始化按钮文字
function initAttendanceButtons() {
const btnAbsent = document.getElementById('btnAbsent');
const btnLate = document.getElementById('btnLate');
const btnLeave = document.getElementById('btnLeave');
if (btnAbsent) btnAbsent.textContent = '缺勤(' + attendanceDeductionMap.absent + '分)';
if (btnLate) btnLate.textContent = '迟到(' + attendanceDeductionMap.late + '分)';
if (btnLeave) btnLeave.textContent = '请假(' + (attendanceDeductionMap.leave > 0 ? attendanceDeductionMap.leave + '分' : '不扣分') + ')';
// 默认选中缺勤,自动填入默认扣分
if (attendanceDeductionMap.absent > 0) {
document.getElementById('customDeduction').value = attendanceDeductionMap.absent;
}
}
// 选择考勤状态
function selectStatus(btn) {
document.querySelectorAll('.status-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentStatus = btn.dataset.status;
// 自动设置默认扣分值(从配置读取)
const defaultDeduction = attendanceDeductionMap[currentStatus] || 0;
if (defaultDeduction > 0) {
document.getElementById('customDeduction').value = defaultDeduction;
} else {
document.getElementById('customDeduction').value = '';
}
}
// 加载学生列表
async function loadStudents() {
const res = await apiGet('/api/admin/students', {page_size: 1000});
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() {
const currentSlot = document.getElementById('attendanceSlot').value;
let html = '';
studentsData.forEach(student => {
const hasRecord = existingRecords.some(r => r.student_id === student.student_id && r.slot === currentSlot);
html += `<div class="student-cell${hasRecord ? ' has-record' : ''}"
data-id="${student.student_id}"
data-name="${escapeHtml(student.name)}"
onclick="toggleStudent(this)">
<span class="student-cell-name">${escapeHtml(student.name)}</span>
<span class="student-cell-no">${escapeHtml(student.student_no)}</span>
</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 slot = document.getElementById('attendanceSlot').value;
const res = await apiGet('/api/admin/attendance/records', { date, slot });
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 slot = document.getElementById('attendanceSlot').value;
const reason = document.getElementById('attendanceReason').value;
const customDeduction = document.getElementById('customDeduction').value;
const customDeductionValue = customDeduction ? parseInt(customDeduction) : null;
// 批量提交
const promises = [];
selectedCells.forEach(cell => {
const studentId = parseInt(cell.dataset.id);
const payload = {
student_id: studentId,
date: date,
slot: slot,
status: currentStatus,
reason: reason,
apply_deduction: true
};
// 只有设置了自定义扣分时才发送
if (customDeductionValue !== null && customDeductionValue > 0) {
payload.custom_deduction = customDeductionValue;
}
promises.push(apiPost('/api/admin/attendance', payload));
});
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();
});
document.getElementById('attendanceSlot').addEventListener('change', function() {
loadExistingRecords();
});
// 页面初始化
initAttendanceButtons();
loadStudents();
loadAttendanceRecords();
</script>
<script src="/assets/js/admin.js"></script>
<?php include __DIR__ . '/../includes/footer.php'; ?>