v0.5测试

This commit is contained in:
2026-04-14 11:35:56 +08:00
parent 2f271b1dc4
commit bf5b2516e2
17 changed files with 646 additions and 439 deletions

View File

@@ -31,4 +31,22 @@ SESSION_TIMEOUT=30
# 是否启用ICP备案号显示 - true/false
ICP_ENABLED=false
# ICP备案号
ICP_NUMBER=京ICP备1234567890号-x
ICP_NUMBER=京ICP备1234567890号-x
# ===========================================
# 扣分规则配置
# ===========================================
# 作业-未交扣分
DEDUCTION_HOMEWORK_NOT_SUBMIT=2
# 作业-迟交扣分
DEDUCTION_HOMEWORK_LATE=1
# 作业-每次加减分上限(绝对值)
HOMEWORK_MAX_POINTS=3
# 考勤-缺勤扣分
DEDUCTION_ATTENDANCE_ABSENT=5
# 考勤-迟到扣分
DEDUCTION_ATTENDANCE_LATE=2
# 考勤-请假扣分
DEDUCTION_ATTENDANCE_LEAVE=1

View File

@@ -27,23 +27,7 @@ $page_title = '管理员管理';
include __DIR__ . '/../includes/header.php';
?>
<div class="nav">
<a href="/admin/dashboard.php" class="nav-item">首页</a>
<a href="/admin/students.php" class="nav-item">学生管理</a>
<?php if ($role === '班主任' || $role === '班长'): ?>
<a href="/admin/conduct.php" class="nav-item">操行分管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '学习委员'): ?>
<a href="/admin/homework.php" class="nav-item">作业管理</a>
<a href="/admin/subjects.php" class="nav-item">科目管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '考勤委员'): ?>
<a href="/admin/attendance.php" class="nav-item">考勤管理</a>
<?php endif; ?>
<a href="/admin/admins.php" class="nav-item active">管理员管理</a>
<a href="/admin/history.php" class="nav-item">历史记录</a>
<a href="/admin/password.php" class="nav-item">修改密码</a>
</div>
<?php include __DIR__ . '/../includes/nav.php'; ?>
<div class="container">
<div class="card">

View File

@@ -28,34 +28,40 @@ if (!in_array($role, ['班主任', '考勤委员'])) {
include __DIR__ . '/../includes/header.php';
?>
<div class="nav">
<a href="/admin/dashboard.php" class="nav-item">首页</a>
<a href="/admin/students.php" class="nav-item">学生管理</a>
<?php if ($role === '班主任' || $role === '班长'): ?>
<a href="/admin/conduct.php" class="nav-item">操行分管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '科代表'): ?>
<a href="/admin/homework.php" class="nav-item">作业管理</a>
<?php endif; ?>
<a href="/admin/attendance.php" class="nav-item active">考勤管理</a>
<?php if ($role === '班主任'): ?>
<a href="/admin/subjects.php" class="nav-item">科目管理</a>
<a href="/admin/admins.php" class="nav-item">管理员管理</a>
<?php endif; ?>
<a href="/admin/history.php" class="nav-item">历史记录</a>
<a href="/admin/password.php" class="nav-item">修改密码</a>
</div>
<?php include __DIR__ . '/../includes/nav.php'; ?>
<div class="container">
<!-- 考勤操作工具栏 -->
<div class="card">
<div class="action-bar">
<button class="btn btn-primary" onclick="showAddAttendanceModal()">添加考勤</button>
<div class="search-bar">
<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'); ?>">
<button class="btn btn-primary" onclick="loadAttendanceRecords()">查询</button>
</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>
@@ -67,54 +73,151 @@ include __DIR__ . '/../includes/header.php';
</div>
</div>
<!-- 添加考勤模态框 -->
<div id="addAttendanceModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>添加考勤记录</h3>
<button class="modal-close" onclick="closeModal('addAttendanceModal')">&times;</button>
</div>
<form onsubmit="event.preventDefault(); submitAddAttendance()">
<div class="form-group">
<label>学生</label>
<select id="attendanceStudentId" required></select>
</div>
<div class="form-group">
<label>日期</label>
<input type="date" id="attAttendanceDate" required>
</div>
<div class="form-group">
<label>状态</label>
<select id="attendanceStatus" required>
<option value="present">出勤</option>
<option value="absent">缺勤</option>
<option value="late">迟到</option>
<option value="leave">请假</option>
</select>
</div>
<div class="form-group">
<label>原因</label>
<input type="text" id="attendanceReason" placeholder="缺勤/迟到/请假原因">
</div>
<div class="form-group">
<label><input type="checkbox" id="attendanceDeduct"> 同时扣分</label>
<small>扣分规则:缺勤-5分迟到-2分请假-1分</small>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">提交</button>
<button type="button" class="btn" onclick="closeModal('addAttendanceModal')">取消</button>
</div>
</form>
</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 = '';
res.data.records.forEach(record => {
const records = res.data.records || [];
records.forEach(record => {
html += `<tr>
<td>${escapeHtml(record.student_no)}</td>
<td>${escapeHtml(record.student_name)}</td>
@@ -124,61 +227,23 @@ async function loadAttendanceRecords() {
<td>${record.deduction_applied ? '已扣分' : '-'}</td>
</tr>`;
});
if (res.data.records.length === 0) {
if (records.length === 0) {
html = '<tr><td colspan="6" style="text-align:center;">暂无考勤记录</td></tr>';
}
document.getElementById('attendanceList').innerHTML = html;
}
}
async function loadStudentsForSelect() {
const res = await apiGet('/api/admin/students');
if (res && res.success) {
let html = '<option value="">请选择学生</option>';
res.data.students.forEach(s => {
html += `<option value="${s.student_id}">${escapeHtml(s.student_no)} - ${escapeHtml(s.name)}</option>`;
});
document.getElementById('attendanceStudentId').innerHTML = html;
}
}
async function showAddAttendanceModal() {
await loadStudentsForSelect();
document.getElementById('addAttendanceModal').style.display = 'flex';
document.getElementById('attAttendanceDate').value = new Date().toISOString().split('T')[0];
}
async function submitAddAttendance() {
const studentId = document.getElementById('attendanceStudentId').value;
const date = document.getElementById('attAttendanceDate').value;
const status = document.getElementById('attendanceStatus').value;
const reason = document.getElementById('attendanceReason').value;
const applyDeduction = document.getElementById('attendanceDeduct').checked;
if (!studentId || !date || !status) {
showToast('请填写完整信息', 'warning');
return;
}
const res = await apiPost('/api/admin/attendance', {
student_id: parseInt(studentId),
date: date,
status: status,
reason: reason,
apply_deduction: applyDeduction
});
if (res && res.success) {
showToast('考勤记录添加成功');
closeModal('addAttendanceModal');
loadAttendanceRecords();
} else {
showToast(res?.message || '添加失败', 'error');
}
}
// 日期变化时重新加载
document.getElementById('attendanceDate').addEventListener('change', function() {
loadExistingRecords();
loadAttendanceRecords();
});
// 页面初始化
loadStudents();
loadAttendanceRecords();
</script>
<script src="/assets/js/admin.js"></script>
<?php include __DIR__ . '/../includes/footer.php'; ?>
<?php include __DIR__ . '/../includes/footer.php'; ?>

View File

@@ -28,23 +28,7 @@ if (!in_array($role, ['班主任', '班长'])) {
include __DIR__ . '/../includes/header.php';
?>
<div class="nav">
<a href="/admin/dashboard.php" class="nav-item">首页</a>
<a href="/admin/students.php" class="nav-item">学生管理</a>
<a href="/admin/conduct.php" class="nav-item active">操行分管理</a>
<?php if ($role === '班主任' || $role === '科代表'): ?>
<a href="/admin/homework.php" class="nav-item">作业管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '考勤委员'): ?>
<a href="/admin/attendance.php" class="nav-item">考勤管理</a>
<?php endif; ?>
<?php if ($role === '班主任'): ?>
<a href="/admin/subjects.php" class="nav-item">科目管理</a>
<a href="/admin/admins.php" class="nav-item">管理员管理</a>
<?php endif; ?>
<a href="/admin/history.php" class="nav-item">历史记录</a>
<a href="/admin/password.php" class="nav-item">修改密码</a>
</div>
<?php include __DIR__ . '/../includes/nav.php'; ?>
<div class="container">
<div class="card">

View File

@@ -22,25 +22,7 @@ $role = $_SESSION['role'] ?? '';
include __DIR__ . '/../includes/header.php';
?>
<div class="nav">
<a href="/admin/dashboard.php" class="nav-item active">首页</a>
<a href="/admin/students.php" class="nav-item">学生管理</a>
<?php if ($role === '班主任' || $role === '班长'): ?>
<a href="/admin/conduct.php" class="nav-item">操行分管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '学习委员'): ?>
<a href="/admin/homework.php" class="nav-item">作业管理</a>
<a href="/admin/subjects.php" class="nav-item">科目管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '考勤委员'): ?>
<a href="/admin/attendance.php" class="nav-item">考勤管理</a>
<?php endif; ?>
<?php if ($role === '班主任'): ?>
<a href="/admin/admins.php" class="nav-item">管理员管理</a>
<?php endif; ?>
<a href="/admin/history.php" class="nav-item">历史记录</a>
<a href="/admin/passwork.php" class="nav-item">修改密码</a>
</div>
<?php include __DIR__ . '/../includes/nav.php'; ?>
<div class="container">
<div class="stats-grid" id="dashboardStats"></div>

View File

@@ -22,25 +22,7 @@ $role = $_SESSION['role'] ?? '';
include __DIR__ . '/../includes/header.php';
?>
<div class="nav">
<a href="/admin/dashboard.php" class="nav-item">首页</a>
<a href="/admin/students.php" class="nav-item">学生管理</a>
<?php if ($role === '班主任' || $role === '班长'): ?>
<a href="/admin/conduct.php" class="nav-item">操行分管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '科代表'): ?>
<a href="/admin/homework.php" class="nav-item">作业管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '考勤委员'): ?>
<a href="/admin/attendance.php" class="nav-item">考勤管理</a>
<?php endif; ?>
<?php if ($role === '班主任'): ?>
<a href="/admin/subjects.php" class="nav-item">科目管理</a>
<a href="/admin/admins.php" class="nav-item">管理员管理</a>
<?php endif; ?>
<a href="/admin/history.php" class="nav-item active">历史记录</a>
<a href="/admin/password.php" class="nav-item">修改密码</a>
</div>
<?php include __DIR__ . '/../includes/nav.php'; ?>
<div class="container">
<div class="card">

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();
}
@@ -28,142 +28,152 @@ if (!in_array($role, ['班主任', '科代表'])) {
include __DIR__ . '/../includes/header.php';
?>
<div class="nav">
<a href="/admin/dashboard.php" class="nav-item">首页</a>
<a href="/admin/students.php" class="nav-item">学生管理</a>
<?php if ($role === '班主任' || $role === '班长'): ?>
<a href="/admin/conduct.php" class="nav-item">操行分管理</a>
<?php endif; ?>
<a href="/admin/homework.php" class="nav-item active">作业管理</a>
<?php if ($role === '班主任' || $role === '考勤委员'): ?>
<a href="/admin/attendance.php" class="nav-item">考勤管理</a>
<?php endif; ?>
<?php if ($role === '班主任'): ?>
<a href="/admin/subjects.php" class="nav-item">科目管理</a>
<a href="/admin/admins.php" class="nav-item">管理员管理</a>
<?php endif; ?>
<a href="/admin/history.php" class="nav-item">历史记录</a>
<a href="/admin/password.php" class="nav-item">修改密码</a>
</div>
<?php include __DIR__ . '/../includes/nav.php'; ?>
<div class="container">
<div class="card">
<div class="action-bar">
<?php if ($role === '班主任'): ?>
<button class="btn btn-primary" onclick="showAddAssignmentModal()">发布作业</button>
<?php endif; ?>
<div class="action-buttons">
<button class="btn btn-primary" onclick="showBatchPointsModal()">批量加减分</button>
</div>
</div>
<div class="table-wrapper">
<table class="table">
<thead>
<tr>
<th><input type="checkbox" id="selectAll" onclick="toggleSelectAll()"></th>
<th>学号</th>
<th>姓名</th>
<th>当前操行分</th>
<th>操作</th>
</tr>
</thead>
<tbody id="studentList"></tbody>
</table>
</div>
<div id="assignmentsList"></div>
</div>
</div>
<!-- 发布作业模态框 -->
<div id="addAssignmentModal" class="modal">
<div id="batchPointsModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>发布作业</h3>
<button class="modal-close" onclick="closeModal('addAssignmentModal')">&times;</button>
<h3>批量加减分</h3>
<button class="modal-close" onclick="closeModal('batchPointsModal')">&times;</button>
</div>
<form onsubmit="event.preventDefault(); submitAddAssignment()">
<form onsubmit="event.preventDefault(); handleSubmitPoints()">
<div class="form-group">
<label>科目</label>
<select id="assignmentSubjectId" required></select>
<label>已选学生</label>
<div id="selectedStudentsCount" class="selected-info">未选择学生</div>
</div>
<div class="form-group">
<label>作业标题</label>
<input type="text" id="assignmentTitle" required>
<label>扣分类型</label>
<div class="deduction-types">
<button type="button" class="btn btn-sm" onclick="selectDeductionType(-window.DEDUCTION_HOMEWORK_NOT_SUBMIT, '未交作业')">未交作业(-<span class="hw-not-submit"></span>分)</button>
<button type="button" class="btn btn-sm" onclick="selectDeductionType(-window.DEDUCTION_HOMEWORK_LATE, '迟交作业')">迟交作业(-<span class="hw-late"></span>分)</button>
<button type="button" class="btn btn-sm" onclick="selectDeductionType(0, '')">自定义</button>
</div>
</div>
<div class="form-group">
<label>作业描述</label>
<textarea id="assignmentDescription" rows="3"></textarea>
<label>分数变动</label>
<input type="number" id="pointsChange" required min="-3" max="3" step="1" placeholder="正数加分,负数扣分">
<small>每次加减分不超过<span class="hw-max"></span>分</small>
</div>
<div class="form-group">
<label>截止日期</label>
<input type="date" id="assignmentDeadline" required>
<label>原因</label>
<textarea id="pointsReason" rows="3" required placeholder="请输入加减分原因"></textarea>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">发布</button>
<button type="button" class="btn" onclick="closeModal('addAssignmentModal')">取消</button>
<button type="submit" class="btn btn-primary">确认提交</button>
<button type="button" class="btn" onclick="closeModal('batchPointsModal')">取消</button>
</div>
</form>
</div>
</div>
<script>
async function loadAssignments() {
const res = await apiGet('/api/admin/homework/assignments');
if (res && res.success) {
let html = '';
for (const assignment of res.data.assignments) {
html += `
<div class="card assignment-card">
<div class="assignment-header">
<div><span class="assignment-title">${escapeHtml(assignment.title)}</span> <span class="assignment-meta">(${escapeHtml(assignment.subject_name)})</span></div>
<div class="assignment-meta">截止: ${assignment.deadline}</div>
</div>
<div class="table-wrapper">
<table class="table">
<thead><tr><th>学生</th><th>状态</th><th>备注</th><th>操作</th></tr></thead>
<tbody id="submissions-${assignment.assignment_id}"><tr><td colspan="4" class="loading">加载中...</td></tr></tbody>
</table>
</div>
</div>
`;
}
if (res.data.assignments.length === 0) {
html = '<div style="text-align:center;padding:40px;">暂无作业</div>';
}
document.getElementById('assignmentsList').innerHTML = html;
for (const assignment of res.data.assignments) {
await loadSubmissions(assignment.assignment_id);
}
}
}
let selectedStudentIds = [];
async function loadSubmissions(assignmentId) {
const res = await apiGet(`/api/admin/homework/submissions/${assignmentId}`);
// 初始化扣分配置
const hwMaxPoints = window.HOMEWORK_MAX_POINTS || 3;
const hwNotSubmit = window.DEDUCTION_HOMEWORK_NOT_SUBMIT || 2;
const hwLate = window.DEDUCTION_HOMEWORK_LATE || 1;
// 更新页面中的配置值显示
document.querySelectorAll('.hw-not-submit').forEach(el => el.textContent = hwNotSubmit);
document.querySelectorAll('.hw-late').forEach(el => el.textContent = hwLate);
document.querySelectorAll('.hw-max').forEach(el => el.textContent = hwMaxPoints);
// 更新输入框的 min/max
document.getElementById('pointsChange').setAttribute('min', -hwMaxPoints);
document.getElementById('pointsChange').setAttribute('max', hwMaxPoints);
async function loadStudents() {
const res = await apiGet('/api/admin/students');
if (res && res.success) {
let html = '';
res.data.submissions.forEach(sub => {
res.data.students.forEach(student => {
html += `<tr>
<td>${escapeHtml(sub.student_name)}</td>
<td>${getStatusBadge(sub.status, 'homework')}</td>
<td>${escapeHtml(sub.comments || '-')}</td>
<td>
<select id="status-${sub.submission_id}" class="status-select">
<option value="submitted" ${sub.status === 'submitted' ? 'selected' : ''}>已提交</option>
<option value="not_submitted" ${sub.status === 'not_submitted' ? 'selected' : ''}>未提交</option>
<option value="late" ${sub.status === 'late' ? 'selected' : ''}>迟交</option>
</select>
<label><input type="checkbox" id="deduct-${sub.submission_id}"> 同时扣分</label>
<button class="btn btn-sm btn-primary" onclick="updateSubmission(${sub.submission_id})">更新</button>
</td>
<td><input type="checkbox" class="student-checkbox" data-id="${student.student_id}"></td>
<td>${escapeHtml(student.student_no)}</td>
<td>${escapeHtml(student.name)}</td>
<td>${student.total_points}</td>
<td><button class="btn btn-sm btn-primary" onclick="showSinglePointsModal(${student.student_id}, '${escapeHtml(student.name)}')">加减分</button></td>
</tr>`;
});
document.getElementById(`submissions-${assignmentId}`).innerHTML = html;
if (res.data.students.length === 0) {
html = '<tr><td colspan="5" style="text-align:center;">暂无学生数据</td></tr>';
}
document.getElementById('studentList').innerHTML = html;
}
}
async function updateSubmission(submissionId) {
const status = document.getElementById(`status-${submissionId}`).value;
const applyDeduction = document.getElementById(`deduct-${submissionId}`).checked;
const res = await apiPut('/api/admin/homework/submission', {
submission_id: submissionId,
status: status,
apply_deduction: applyDeduction
function showSinglePointsModal(studentId, studentName) {
selectedStudentIds = [studentId];
document.getElementById('selectedStudentsCount').innerHTML = `${studentName} (1人)`;
document.getElementById('pointsChange').value = '';
document.getElementById('pointsReason').value = '';
document.getElementById('batchPointsModal').style.display = 'flex';
}
function showBatchPointsModal() {
selectedStudentIds = [];
document.querySelectorAll('.student-checkbox:checked').forEach(cb => {
selectedStudentIds.push(parseInt(cb.dataset.id));
});
if (res && res.success) {
showToast('更新成功');
loadAssignments();
if (selectedStudentIds.length === 0) {
showToast('请先选择学生', 'warning');
return;
}
document.getElementById('selectedStudentsCount').innerHTML = `已选择 ${selectedStudentIds.length} 名学生`;
document.getElementById('pointsChange').value = '';
document.getElementById('pointsReason').value = '';
document.getElementById('batchPointsModal').style.display = 'flex';
}
function selectDeductionType(points, reason) {
document.getElementById('pointsChange').value = points;
if (points !== 0) {
document.getElementById('pointsReason').value = reason;
} else {
showToast(res?.message || '更新失败', 'error');
document.getElementById('pointsReason').value = '';
document.getElementById('pointsReason').focus();
}
}
loadAssignments();
function handleSubmitPoints() {
const pointsChange = parseInt(document.getElementById('pointsChange').value);
if (isNaN(pointsChange) || pointsChange === 0) {
showToast('请输入有效的加减分值', 'warning');
return;
}
if (Math.abs(pointsChange) > hwMaxPoints) {
showToast(`每次加减分不超过${hwMaxPoints}分`, 'warning');
return;
}
submitBatchPoints();
}
loadStudents();
loadStudents();
</script>
<script src="/assets/js/admin.js"></script>

View File

@@ -22,25 +22,7 @@ $role = $_SESSION['role'] ?? '';
include __DIR__ . '/../includes/header.php';
?>
<div class="nav">
<a href="/admin/dashboard.php" class="nav-item">首页</a>
<a href="/admin/students.php" class="nav-item">学生管理</a>
<?php if ($role === '班主任' || $role === '班长'): ?>
<a href="/admin/conduct.php" class="nav-item">操行分管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '科代表'): ?>
<a href="/admin/homework.php" class="nav-item">作业管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '考勤委员'): ?>
<a href="/admin/attendance.php" class="nav-item">考勤管理</a>
<?php endif; ?>
<?php if ($role === '班主任'): ?>
<a href="/admin/subjects.php" class="nav-item">科目管理</a>
<a href="/admin/admins.php" class="nav-item">管理员管理</a>
<?php endif; ?>
<a href="/admin/history.php" class="nav-item">历史记录</a>
<a href="/admin/password.php" class="nav-item active">修改密码</a>
</div>
<?php include __DIR__ . '/../includes/nav.php'; ?>
<div class="container">
<div class="card">

View File

@@ -22,25 +22,7 @@ $role = $_SESSION['role'] ?? '';
include __DIR__ . '/../includes/header.php';
?>
<div class="nav">
<a href="/admin/dashboard.php" class="nav-item">首页</a>
<a href="/admin/students.php" class="nav-item active">学生管理</a>
<?php if ($role === '班主任' || $role === '班长'): ?>
<a href="/admin/conduct.php" class="nav-item">操行分管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '科代表'): ?>
<a href="/admin/homework.php" class="nav-item">作业管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '考勤委员'): ?>
<a href="/admin/attendance.php" class="nav-item">考勤管理</a>
<?php endif; ?>
<?php if ($role === '班主任'): ?>
<a href="/admin/subjects.php" class="nav-item">科目管理</a>
<a href="/admin/admins.php" class="nav-item">管理员管理</a>
<?php endif; ?>
<a href="/admin/history.php" class="nav-item">历史记录</a>
<a href="/admin/password.php" class="nav-item">修改密码</a>
</div>
<?php include __DIR__ . '/../includes/nav.php'; ?>
<div class="container">
<div class="card">
@@ -65,7 +47,7 @@ include __DIR__ . '/../includes/header.php';
<th>学号</th>
<th>姓名</th>
<th>操行分</th>
<th>家长手机号</th>
<?php if ($role === '班主任'): ?><th>家长手机号</th><?php endif; ?>
<th>操作</th>
</tr>
</thead>
@@ -129,6 +111,7 @@ include __DIR__ . '/../includes/header.php';
</div>
<script>
const userRole = '<?php echo $role; ?>';
let currentPage = 1;
let totalPages = 1;
@@ -145,7 +128,7 @@ async function loadStudents(page = 1) {
<td>${escapeHtml(student.student_no)}</td>
<td>${escapeHtml(student.name)}</td>
<td>${student.total_points}</td>
<td>${student.parent_phone || '-'}</td>
${userRole === '班主任' ? `<td>${student.parent_phone || '-'}</td>` : ''}
<td>
<button class="btn btn-sm btn-primary" onclick="showSinglePointsModal(${student.student_id}, '${escapeHtml(student.name)}')">加减分</button>
</td>
@@ -153,7 +136,7 @@ async function loadStudents(page = 1) {
});
if (res.data.students.length === 0) {
html = '<tr><td colspan="6" style="text-align:center;">暂无学生数据</td></tr>';
html = `<tr><td colspan="${userRole === '班主任' ? '6' : '5'}" style="text-align:center;">暂无学生数据</td></tr>`;
}
document.getElementById('studentList').innerHTML = html;

View File

@@ -27,25 +27,7 @@ $page_title = '科目管理';
include __DIR__ . '/../includes/header.php';
?>
<div class="nav">
<a href="/admin/dashboard.php" class="nav-item">首页</a>
<a href="/admin/students.php" class="nav-item">学生管理</a>
<?php if ($role === '班主任' || $role === '班长'): ?>
<a href="/admin/conduct.php" class="nav-item">操行分管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '学习委员'): ?>
<a href="/admin/homework.php" class="nav-item">作业管理</a>
<a href="/admin/subjects.php" class="nav-item active">科目管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '考勤委员'): ?>
<a href="/admin/attendance.php" class="nav-item">考勤管理</a>
<?php endif; ?>
<?php if ($role === '班主任'): ?>
<a href="/admin/admins.php" class="nav-item">管理员管理</a>
<?php endif; ?>
<a href="/admin/history.php" class="nav-item">历史记录</a>
<a href="/admin/password.php" class="nav-item">修改密码</a>
</div>
<?php include __DIR__ . '/../includes/nav.php'; ?>
<div class="container">
<div class="card">

View File

@@ -127,4 +127,86 @@
width: 18px;
height: 18px;
cursor: pointer;
}
}
/* 考勤学生方格网格 */
.student-grid {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 15px 0;
}
.student-cell {
width: calc(100% / 7 - 10px);
min-height: 60px;
border: 2px solid #e5e7eb;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
padding: 8px 4px;
text-align: center;
word-break: break-all;
user-select: none;
}
.student-cell:hover {
background: #f3f4f6;
border-color: #9ca3af;
}
.student-cell.selected {
background: #fee2e2;
border-color: #ef4444;
color: #dc2626;
}
.student-cell.has-record {
border: 2px dashed #9ca3af;
opacity: 0.7;
}
.attendance-toolbar {
display: flex;
align-items: center;
gap: 10px;
margin: 15px 0;
flex-wrap: wrap;
}
.attendance-toolbar .status-group {
display: flex;
gap: 8px;
}
.attendance-toolbar .status-btn {
padding: 6px 16px;
border: 2px solid #e5e7eb;
border-radius: 6px;
background: white;
cursor: pointer;
font-size: 13px;
transition: all 0.2s;
}
.attendance-toolbar .status-btn.active {
border-color: #667eea;
background: #eef2ff;
color: #4338ca;
}
@media (max-width: 768px) {
.student-cell {
width: calc(100% / 4 - 10px);
}
}
@media (max-width: 480px) {
.student-cell {
width: calc(100% / 3 - 10px);
}
}

View File

@@ -171,103 +171,6 @@ async function submitAddStudent() {
}
}
// 显示添加作业模态框
function showAddAssignmentModal() {
document.getElementById('addAssignmentModal').style.display = 'flex';
loadSubjectsForSelect();
}
// 加载科目下拉框
async function loadSubjectsForSelect() {
const res = await apiGet('/api/subject/list');
if (res && res.success) {
let html = '<option value="">请选择科目</option>';
res.data.subjects.forEach(s => {
if (s.is_active) {
html += `<option value="${s.subject_id}">${s.subject_name}</option>`;
}
});
document.getElementById('assignmentSubjectId').innerHTML = html;
}
}
// 提交添加作业
async function submitAddAssignment() {
const subjectId = document.getElementById('assignmentSubjectId').value;
const title = document.getElementById('assignmentTitle').value.trim();
const description = document.getElementById('assignmentDescription').value;
const deadline = document.getElementById('assignmentDeadline').value;
if (!subjectId || !title || !deadline) {
showToast('请填写完整信息', 'warning');
return;
}
const res = await apiPost('/api/admin/homework/assignment', {
subject_id: parseInt(subjectId),
title: title,
description: description,
deadline: deadline
});
if (res && res.success) {
showToast('作业发布成功');
closeModal('addAssignmentModal');
loadAssignments();
} else {
showToast(res?.message || '发布失败', 'error');
}
}
// 显示添加考勤模态框
async function showAddAttendanceModal() {
await loadStudentsForSelect();
document.getElementById('addAttendanceModal').style.display = 'flex';
document.getElementById('attendanceDate').value = new Date().toISOString().split('T')[0];
}
// 加载学生下拉框
async function loadStudentsForSelect() {
const res = await apiGet('/api/admin/students');
if (res && res.success) {
let html = '<option value="">请选择学生</option>';
res.data.students.forEach(s => {
html += `<option value="${s.student_id}">${s.student_no} - ${s.name}</option>`;
});
document.getElementById('attendanceStudentId').innerHTML = html;
}
}
// 提交添加考勤
async function submitAddAttendance() {
const studentId = document.getElementById('attendanceStudentId').value;
const date = document.getElementById('attendanceDate').value;
const status = document.getElementById('attendanceStatus').value;
const reason = document.getElementById('attendanceReason').value;
const applyDeduction = document.getElementById('attendanceDeduct').checked;
if (!studentId || !date || !status) {
showToast('请填写完整信息', 'warning');
return;
}
const res = await apiPost('/api/admin/attendance', {
student_id: parseInt(studentId),
date: date,
status: status,
reason: reason,
apply_deduction: applyDeduction
});
if (res && res.success) {
showToast('考勤记录添加成功');
closeModal('addAttendanceModal');
loadAttendanceRecords();
} else {
showToast(res?.message || '添加失败', 'error');
}
}
// 显示添加管理员模态框
function showAddAdminModal() {
document.getElementById('addAdminModal').style.display = 'flex';

View File

@@ -61,6 +61,14 @@ define('SESSION_TIMEOUT', (int)$config['SESSION_TIMEOUT']);
define('ICP_ENABLED', $config['ICP_ENABLED'] === 'false');
define('ICP_NUMBER', $config['ICP_NUMBER'] ?? '');
// 扣分规则配置(有默认值,不强制要求在.env中配置
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));
// 会话配置
ini_set('session.cookie_httponly', 1);
ini_set('session.use_only_cookies', 1);

View File

@@ -45,5 +45,11 @@ $page_title = $page_title ?? '首页';
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.DEDUCTION_HOMEWORK_NOT_SUBMIT = <?php echo DEDUCTION_HOMEWORK_NOT_SUBMIT; ?>;
window.DEDUCTION_HOMEWORK_LATE = <?php echo DEDUCTION_HOMEWORK_LATE; ?>;
window.HOMEWORK_MAX_POINTS = <?php echo HOMEWORK_MAX_POINTS; ?>;
window.DEDUCTION_ATTENDANCE_ABSENT = <?php echo DEDUCTION_ATTENDANCE_ABSENT; ?>;
window.DEDUCTION_ATTENDANCE_LATE = <?php echo DEDUCTION_ATTENDANCE_LATE; ?>;
window.DEDUCTION_ATTENDANCE_LEAVE = <?php echo DEDUCTION_ATTENDANCE_LEAVE; ?>;
</script>
<script src="/assets/js/common.js"></script>

21
frontend/includes/nav.php Normal file
View File

@@ -0,0 +1,21 @@
<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 === '班长'): ?>
<a href="/admin/conduct.php" class="nav-item<?php echo $current_page === 'conduct' ? ' active' : ''; ?>">操行分管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '学习委员'): ?>
<a href="/admin/homework.php" class="nav-item<?php echo $current_page === 'homework' ? ' active' : ''; ?>">作业管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '考勤委员'): ?>
<a href="/admin/attendance.php" class="nav-item<?php echo $current_page === 'attendance' ? ' active' : ''; ?>">考勤管理</a>
<?php endif; ?>
<?php if ($role === '班主任' || $role === '学习委员'): ?>
<a href="/admin/subjects.php" class="nav-item<?php echo $current_page === 'subjects' ? ' active' : ''; ?>">科目管理</a>
<?php endif; ?>
<?php if ($role === '班主任'): ?>
<a href="/admin/admins.php" class="nav-item<?php echo $current_page === 'admins' ? ' 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>
</div>