更新v1.4版本,修复了一些已知问题

This commit is contained in:
2026-04-28 03:16:17 +08:00
parent 76088b0dd4
commit 3aac2395a0
26 changed files with 342 additions and 151 deletions

View File

@@ -48,11 +48,11 @@ HOMEWORK_MAX_POINTS=3
STUDY_COMMISSIONER_MAX_POINTS=5
# 考勤-缺勤扣分
DEDUCTION_ATTENDANCE_ABSENT=5
DEDUCTION_ATTENDANCE_ABSENT=3
# 考勤-迟到扣分
DEDUCTION_ATTENDANCE_LATE=2
# 考勤-请假扣分
DEDUCTION_ATTENDANCE_LEAVE=1
DEDUCTION_ATTENDANCE_LATE=1
# 考勤-请假扣分设为0表示不扣分
DEDUCTION_ATTENDANCE_LEAVE=0
# 学生初始操行分
STUDENT_INITIAL_POINTS=60

View File

@@ -227,7 +227,6 @@ async function submitEditAdmin() {
}
const res = await apiPut(`/api/admin/update/${currentEditUserId}`, {
user_id: currentEditUserId,
real_name: document.getElementById('editAdminRealName').value,
role_type: roleType
});

View File

@@ -47,10 +47,10 @@ include __DIR__ . '/../includes/header.php';
</select>
</div>
<div class="status-group">
<button class="status-btn active" data-status="absent" onclick="selectStatus(this)" data-default-deduction="3">缺勤</button>
<button class="status-btn" data-status="late" onclick="selectStatus(this)" data-default-deduction="1">迟到</button>
<button class="status-btn" data-status="leave" onclick="selectStatus(this)" data-default-deduction="0">请假</button>
<input type="number" id="customDeduction" placeholder="自定义扣分" min="0" max="10" style="width:100px;margin-left:10px;" title="留空或0使用默认值">
<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>
@@ -92,14 +92,35 @@ 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 = btn.dataset.defaultDeduction;
if (defaultDeduction && defaultDeduction !== '0') {
// 自动设置默认扣分值(从配置读取)
const defaultDeduction = attendanceDeductionMap[currentStatus] || 0;
if (defaultDeduction > 0) {
document.getElementById('customDeduction').value = defaultDeduction;
} else {
document.getElementById('customDeduction').value = '';
@@ -252,6 +273,7 @@ document.getElementById('attendanceSlot').addEventListener('change', function()
});
// 页面初始化
initAttendanceButtons();
loadStudents();
loadAttendanceRecords();
</script>

View File

@@ -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();
}

View File

@@ -65,7 +65,7 @@ include __DIR__ . '/../includes/header.php';
<th>分数变动</th>
<th>原因</th>
<th>操作人</th>
<?php if ($role === '班主任' || $role === '班长'): ?>
<?php if ($role === '班主任' || $role === '班长' || $role === '考勤委员'): ?>
<th>操作</th>
<?php endif; ?>
</tr>
@@ -80,6 +80,7 @@ include __DIR__ . '/../includes/header.php';
<script>
var currentHistoryPage = 1;
var totalHistoryPages = 1;
var currentUserId = <?php echo intval($_SESSION['user_id']); ?>;
async function loadStudentsForSelect() {
const res = await apiGet('/api/admin/students', {page_size: 1000});
@@ -134,12 +135,20 @@ async function loadHistory(page = 1) {
} else {
html += `<td><button class="btn btn-sm btn-danger" onclick="revokeRecord(${record.record_id})">撤销</button></td>`;
}
<?php elseif ($role === '考勤委员'): ?>
if (record.is_revoked == 1) {
html += `<td><span class="text-muted">已撤销</span></td>`;
} else if (record.recorder_id == currentUserId) {
html += `<td><button class="btn btn-sm btn-danger" onclick="revokeRecord(${record.record_id})">撤销</button></td>`;
} else {
html += `<td><span class="text-muted">-</span></td>`;
}
<?php endif; ?>
html += `</tr>`;
});
if (res.data.records.length === 0) {
const colSpan = <?php echo ($role === '班主任' || $role === '班长') ? '6' : '5'; ?>;
const colSpan = <?php echo ($role === '班主任' || $role === '班长' || $role === '考勤委员') ? '6' : '5'; ?>;
html = `<tr><td colspan="${colSpan}" style="text-align:center;">暂无记录</td></tr>`;
}
@@ -151,22 +160,9 @@ async function loadHistory(page = 1) {
}
function renderHistoryPagination() {
const container = document.getElementById('historyPagination');
if (!container) return;
if (totalHistoryPages <= 1) {
container.innerHTML = '';
return;
}
let html = '';
for (let i = 1; i <= totalHistoryPages; i++) {
if (i === currentHistoryPage) {
html += `<span class="active">${i}</span>`;
} else {
html += `<a href="#" onclick="loadHistory(${i}); return false;">${i}</a>`;
}
}
container.innerHTML = html;
renderSmartPagination('historyPagination', currentHistoryPage, totalHistoryPages, function(page) {
loadHistory(page);
});
}
// 导出历史记录

View File

@@ -457,21 +457,9 @@ async function viewArchiveData(semesterId, semesterName, page) {
}
function renderArchivePagination(semesterId, semesterName) {
const container = document.getElementById('archivePagination');
if (!container) return;
if (archiveTotalPages <= 1) {
container.innerHTML = '';
return;
}
let html = '';
for (let i = 1; i <= archiveTotalPages; i++) {
if (i === archivePage) {
html += `<span class="active">${i}</span>`;
} else {
html += `<a href="#" onclick="viewArchiveData(${semesterId}, '${escapeHtml(semesterName)}', ${i}); return false;">${i}</a>`;
}
}
container.innerHTML = html;
renderSmartPagination('archivePagination', archivePage, archiveTotalPages, function(page) {
viewArchiveData(semesterId, semesterName, page);
});
}
function closeModal(modalId) {

View File

@@ -201,22 +201,9 @@ async function loadStudents(page = 1) {
}
function renderPagination() {
const container = document.getElementById('pagination');
if (!container) return;
if (totalPages <= 1) {
container.innerHTML = '';
return;
}
let html = '';
for (let i = 1; i <= totalPages; i++) {
if (i === currentPage) {
html += `<span class="active">${i}</span>`;
} else {
html += `<a href="#" onclick="loadStudents(${i}); return false;">${i}</a>`;
}
}
container.innerHTML = html;
renderSmartPagination('pagination', currentPage, totalPages, function(page) {
loadStudents(page);
});
}
function showSinglePointsModal(studentId, studentName) {
@@ -263,7 +250,16 @@ document.getElementById('searchInput').addEventListener('input', () => {
<div class="form-group">
<label>分数变动</label>
<input type="number" id="pointsChange" required placeholder="正数为加分,负数为扣分">
<small><?php echo $role === '班长' ? '班长单次±5分以内' : '班主任无限制'; ?></small>
<small><?php
$hints = [
'班长' => '班长单次±5分以内',
'学习委员' => '学习委员单次±5分以内',
'考勤委员' => '考勤委员仅限扣分单次最多扣8分',
'劳动委员' => '劳动委员单次±1分以内',
'志愿委员' => '志愿委员仅限加分,最多+5分',
];
echo $hints[$role] ?? '班主任无限制';
?></small>
</div>
<div class="form-group">
<label>原因</label>

View File

@@ -490,8 +490,10 @@ tr:hover {
.pagination {
display: flex;
justify-content: center;
gap: 8px;
align-items: center;
gap: 6px;
margin-top: 20px;
flex-wrap: wrap;
}
.pagination a, .pagination span {
@@ -501,6 +503,16 @@ tr:hover {
text-decoration: none;
color: #666;
cursor: pointer;
min-width: 36px;
text-align: center;
box-sizing: border-box;
transition: all 0.2s;
}
.pagination a:hover {
background: #f0f0ff;
border-color: #667eea;
color: #667eea;
}
.pagination .active {
@@ -509,6 +521,43 @@ tr:hover {
border-color: #667eea;
}
.pagination .ellipsis {
border: none;
cursor: default;
padding: 6px 4px;
color: #999;
min-width: auto;
}
.pagination .page-jump {
display: flex;
align-items: center;
gap: 4px;
margin-left: 8px;
font-size: 13px;
color: #666;
}
.pagination .page-jump input {
width: 50px;
padding: 5px 8px;
border: 1px solid #ddd;
border-radius: 4px;
text-align: center;
font-size: 13px;
outline: none;
}
.pagination .page-jump input:focus {
border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.15);
}
.pagination .page-nav {
padding: 6px 10px;
font-size: 13px;
}
/* ========== 提示消息 ========== */
.toast {
position: fixed;

View File

@@ -205,6 +205,125 @@ function escapeHtml(str) {
});
}
/**
* 智能分页渲染最多显示7个页码 + 跳转输入框)
* @param {string|HTMLElement} container - 分页容器ID或DOM元素
* @param {number} currentPage - 当前页码
* @param {number} totalPages - 总页数
* @param {function} onPageChange - 页码变化回调函数,参数为新的页码
*/
function renderSmartPagination(container, currentPage, totalPages, onPageChange) {
if (typeof container === 'string') {
container = document.getElementById(container);
}
if (!container || totalPages <= 1) {
if (container) container.innerHTML = '';
return;
}
const MAX_VISIBLE = 7;
let html = '';
// 上一页按钮
if (currentPage > 1) {
html += `<a href="#" class="page-nav" data-page="${currentPage - 1}">&laquo; 上一页</a>`;
}
if (totalPages <= MAX_VISIBLE) {
// 总页数不超过最大显示数,全部显示
for (let i = 1; i <= totalPages; i++) {
if (i === currentPage) {
html += `<span class="active">${i}</span>`;
} else {
html += `<a href="#" data-page="${i}">${i}</a>`;
}
}
} else {
// 需要省略号
// 始终显示第1页
if (currentPage === 1) {
html += `<span class="active">1</span>`;
} else {
html += `<a href="#" data-page="1">1</a>`;
}
// 计算中间页码范围
let start = Math.max(2, currentPage - 2);
let end = Math.min(totalPages - 1, currentPage + 2);
// 调整确保中间至少有3个页码加上首尾共5-7个
if (currentPage <= 3) {
end = Math.min(5, totalPages - 1);
}
if (currentPage >= totalPages - 2) {
start = Math.max(2, totalPages - 4);
}
// 前省略号
if (start > 2) {
html += `<span class="ellipsis">...</span>`;
}
// 中间页码
for (let i = start; i <= end; i++) {
if (i === currentPage) {
html += `<span class="active">${i}</span>`;
} else {
html += `<a href="#" data-page="${i}">${i}</a>`;
}
}
// 后省略号
if (end < totalPages - 1) {
html += `<span class="ellipsis">...</span>`;
}
// 始终显示最后一页
if (currentPage === totalPages) {
html += `<span class="active">${totalPages}</span>`;
} else {
html += `<a href="#" data-page="${totalPages}">${totalPages}</a>`;
}
}
// 下一页按钮
if (currentPage < totalPages) {
html += `<a href="#" class="page-nav" data-page="${currentPage + 1}">下一页 &raquo;</a>`;
}
// 页码跳转
html += `<span class="page-jump">跳至 <input type="number" min="1" max="${totalPages}" placeholder="页码"> / ${totalPages}页</span>`;
container.innerHTML = html;
// 绑定页码点击事件
container.querySelectorAll('a[data-page]').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
const page = parseInt(this.dataset.page);
if (page && page !== currentPage && page >= 1 && page <= totalPages) {
onPageChange(page);
}
});
});
// 绑定跳转输入框事件
const jumpInput = container.querySelector('.page-jump input');
if (jumpInput) {
jumpInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
const page = parseInt(this.value);
if (page && page >= 1 && page <= totalPages) {
onPageChange(page);
} else {
showToast(`请输入1-${totalPages}之间的页码`, 'warning');
}
}
});
}
}
document.addEventListener('DOMContentLoaded', () => {
const user = getUserInfo();
const userNameSpan = document.getElementById('userName');

View File

@@ -65,9 +65,9 @@ define('ICP_NUMBER', $config['ICP_NUMBER'] ?? '');
define('DEDUCTION_HOMEWORK_NOT_SUBMIT', (int)($config['DEDUCTION_HOMEWORK_NOT_SUBMIT'] ?? 2));
define('DEDUCTION_HOMEWORK_LATE', (int)($config['DEDUCTION_HOMEWORK_LATE'] ?? 1));
define('HOMEWORK_MAX_POINTS', (int)($config['HOMEWORK_MAX_POINTS'] ?? 3));
define('DEDUCTION_ATTENDANCE_ABSENT', (int)($config['DEDUCTION_ATTENDANCE_ABSENT'] ?? 5));
define('DEDUCTION_ATTENDANCE_LATE', (int)($config['DEDUCTION_ATTENDANCE_LATE'] ?? 2));
define('DEDUCTION_ATTENDANCE_LEAVE', (int)($config['DEDUCTION_ATTENDANCE_LEAVE'] ?? 1));
define('DEDUCTION_ATTENDANCE_ABSENT', (int)($config['DEDUCTION_ATTENDANCE_ABSENT'] ?? 3));
define('DEDUCTION_ATTENDANCE_LATE', (int)($config['DEDUCTION_ATTENDANCE_LATE'] ?? 1));
define('DEDUCTION_ATTENDANCE_LEAVE', (int)($config['DEDUCTION_ATTENDANCE_LEAVE'] ?? 0));
// 学生初始操行分
define('STUDENT_INITIAL_POINTS', (int)($config['STUDENT_INITIAL_POINTS'] ?? 60));

View File

@@ -1,7 +1,7 @@
<div class="nav">
<a href="/admin/dashboard.php" class="nav-item<?php echo $current_page === 'dashboard' ? ' active' : ''; ?>">首页</a>
<a href="/admin/students.php" class="nav-item<?php echo $current_page === 'students' ? ' active' : ''; ?>">学生管理</a>
<?php if ($role === '班主任' || $role === '班长' || $role === '劳动委员' || $role === '志愿委员'): ?>
<?php if ($role === '班主任' || $role === '班长' || $role === '考勤委员' || $role === '劳动委员' || $role === '志愿委员'): ?>
<a href="/admin/conduct.php" class="nav-item<?php echo $current_page === 'conduct' ? ' active' : ''; ?>">操行分管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '学习委员'): ?>

View File

@@ -133,6 +133,5 @@ if (isset($_SESSION['user_id']) && isset($_SESSION['user_type'])) {
}, 3000);
}
</script>
</script>
</body>
</html>

View File

@@ -73,7 +73,6 @@ async function loadDashboard() {
document.getElementById('totalPoints').textContent = res.data.total_points;
}
// 加载排名信息
// 加载排名信息
const rankRes = await apiGet('/api/parent/child/ranking');
if (rankRes && rankRes.success) {

View File

@@ -285,7 +285,7 @@ include __DIR__ . '/../includes/header.php';
html += `
<div class="record-item">
<span class="record-points ${pointsClass}">${record.points_change > 0 ? '+' : ''}${record.points_change}</span>
<span class="record-reason">${record.reason}</span>
<span class="record-reason">${escapeHtml(record.reason)}</span>
<span class="record-time">${formatDate(record.created_at)}</span>
</div>
`;
@@ -347,8 +347,8 @@ include __DIR__ . '/../includes/header.php';
<tr>
<td>${formatDateTime(record.created_at)}</td>
<td class="record-points ${pointsClass}">${record.points_change > 0 ? '+' : ''}${record.points_change}</td>
<td>${record.reason}</td>
<td>${record.recorder_name}</td>
<td>${escapeHtml(record.reason)}</td>
<td>${escapeHtml(record.recorder_name)}</td>
</tr>
`;
});
@@ -369,21 +369,9 @@ include __DIR__ . '/../includes/header.php';
}
function renderConductPagination() {
const container = document.getElementById('conductPagination');
if (!container) return;
if (conductTotalPages <= 1) {
container.innerHTML = '';
return;
}
let html = '';
for (let i = 1; i <= conductTotalPages; i++) {
if (i === conductPage) {
html += `<span class="active">${i}</span>`;
} else {
html += `<a href="#" onclick="loadConductHistory(${i}); return false;">${i}</a>`;
}
}
container.innerHTML = html;
renderSmartPagination('conductPagination', conductPage, conductTotalPages, function(page) {
loadConductHistory(page);
});
}
// 加载作业
@@ -395,11 +383,11 @@ include __DIR__ . '/../includes/header.php';
res.data.homework.forEach(hw => {
html += `
<tr>
<td>${hw.subject_name}</td>
<td>${hw.title}</td>
<td>${hw.deadline}</td>
<td>${escapeHtml(hw.subject_name)}</td>
<td>${escapeHtml(hw.title)}</td>
<td>${escapeHtml(hw.deadline)}</td>
<td>${getStatusBadge(hw.status, 'homework')}</td>
<td>${hw.comments || '-'}</td>
<td>${escapeHtml(hw.comments || '-')}</td>
</tr>
`;
});
@@ -428,9 +416,9 @@ include __DIR__ . '/../includes/header.php';
res.data.records.forEach(record => {
html += `
<tr>
<td>${record.date}</td>
<td>${escapeHtml(record.date)}</td>
<td>${getStatusBadge(record.status, 'attendance')}</td>
<td>${record.reason || '-'}</td>
<td>${escapeHtml(record.reason || '-')}</td>
</tr>
`;
});