v2.2更新

This commit is contained in:
2026-05-28 15:38:32 +08:00
parent f84c9d3efb
commit ca53fdc349
38 changed files with 688 additions and 686 deletions

View File

@@ -35,6 +35,7 @@ include __DIR__ . '/../includes/header.php';
<div class="action-bar">
<div class="action-buttons">
<button class="btn btn-primary" onclick="showBatchPointsModal()">批量加减分</button>
<button class="btn btn-secondary" onclick="showDormitoryPointsModal()">宿舍加分</button>
<?php if ($role === '班主任'): ?>
<button class="btn btn-secondary" onclick="exportMoralityRecords()">导出德育分记录</button>
<?php endif; ?>
@@ -110,4 +111,40 @@ include __DIR__ . '/../includes/header.php';
</div>
</div>
<!-- 宿舍集体加分模态框 -->
<div id="dormitoryPointsModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>宿舍集体加分</h3>
<button class="modal-close" onclick="closeModal('dormitoryPointsModal')">&times;</button>
</div>
<form onsubmit="event.preventDefault(); submitDormitoryPoints()">
<div class="form-group">
<label>选择宿舍</label>
<select id="dormitorySelect" onchange="onDormitorySelected()" required>
<option value="">-- 请选择宿舍 --</option>
</select>
</div>
<div class="form-group" id="dormitoryStudentsGroup" style="display:none;">
<label>宿舍成员</label>
<div id="dormitoryStudentsList" style="max-height: 150px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: 4px; padding: 8px;">
</div>
<small id="dormitoryStudentsCount"></small>
</div>
<div class="form-group">
<label>分数变动</label>
<input type="number" id="dormitoryPointsChange" required placeholder="正数为加分,负数为扣分">
</div>
<div class="form-group">
<label>原因</label>
<textarea id="dormitoryPointsReason" required rows="3" placeholder="请填写加减分原因"></textarea>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">确认提交</button>
<button type="button" class="btn" onclick="closeModal('dormitoryPointsModal')">取消</button>
</div>
</form>
</div>
</div>
<?php include __DIR__ . '/../includes/footer.php'; ?>

View File

@@ -154,7 +154,7 @@ include __DIR__ . '/../includes/header.php';
}
if (iconEl) iconEl.textContent = '⟳';
fetch('/upgrade.php?action=step&version=' + encodeURIComponent(step.version), { method: 'POST' })
fetch('/api/execute_upgrade.php?action=step&version=' + encodeURIComponent(step.version), { method: 'POST' })
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) {

View File

@@ -54,7 +54,7 @@ include __DIR__ . '/../includes/header.php';
<div class="filter-group" style="min-width:auto;">
<label>&nbsp;</label>
<label style="display:flex;align-items:center;gap:4px;cursor:pointer;font-size:13px;">
<input type="checkbox" id="historyGrouped" onchange="loadHistory(1)"> 批次合并
<input type="checkbox" id="historyGrouped" checked onchange="loadHistory(1)"> 批次合并
</label>
</div>
<?php if ($role === '班主任'): ?>

View File

@@ -1,7 +1,6 @@
<?php
/**
* 检查数据库版本是否需要升级
* 返回 JSON: {needs_upgrade: bool, current: string, target: string}
* 检查数据库版本是否需要升级(代理至后端 API
*/
require_once __DIR__ . '/../config.php';
@@ -20,84 +19,43 @@ if ($role !== '班主任') {
exit();
}
// 读取后端 .env 获取数据库配置
$envPath = __DIR__ . '/../../backend/.env';
if (!file_exists($envPath)) {
echo json_encode(['error' => '数据库配置文件不存在']);
// 从 session 获取 JWT token
$token = $_SESSION['jwt_token'] ?? '';
if (empty($token)) {
echo json_encode(['error' => '会话已过期,请重新登录']);
exit();
}
$lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$dbConfig = [];
foreach ($lines as $line) {
$line = trim($line);
if ($line === '' || strpos($line, '#') === 0) {
continue;
}
if (strpos($line, '=') !== false) {
list($key, $value) = explode('=', $line, 2);
$dbConfig[trim($key)] = trim($value);
}
// 调用后端 API
$apiUrl = API_BASE_URL . '/api/upgrade/check';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $apiUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => API_TIMEOUT,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json'
],
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 0
]);
$apiResponse = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200 || empty($apiResponse)) {
echo json_encode(['error' => '无法连接升级服务']);
exit();
}
$required = ['DB_HOST', 'DB_PORT', 'DB_USER', 'DB_PASSWORD', 'DB_NAME'];
foreach ($required as $key) {
if (!isset($dbConfig[$key]) || $dbConfig[$key] === '') {
echo json_encode(['error' => "缺少数据库配置: {$key}"]);
exit();
}
$result = json_decode($apiResponse, true);
if (!$result || !isset($result['success']) || !$result['success']) {
echo json_encode(['error' => $result['message'] ?? '升级检查失败']);
exit();
}
try {
$dsn = "mysql:host={$dbConfig['DB_HOST']};port={$dbConfig['DB_PORT']};dbname={$dbConfig['DB_NAME']};charset=utf8mb4";
$pdo = new PDO($dsn, $dbConfig['DB_USER'], $dbConfig['DB_PASSWORD'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
// 检测当前版本
$currentVersion = '0.0.0';
try {
$stmt = $pdo->query("SELECT setting_value FROM system_settings WHERE setting_key = 'db_version'");
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($row) {
$currentVersion = $row['setting_value'];
}
} catch (PDOException $e) {
// 表不存在,使用默认值
}
// 读取目标版本
$versionFile = __DIR__ . '/../../VERSION';
if (!file_exists($versionFile)) {
echo json_encode(['error' => 'VERSION 文件不存在']);
exit();
}
$targetVersion = trim(file_get_contents($versionFile));
$needsUpgrade = version_compare($targetVersion, $currentVersion, '>');
$allVersions = [
'1.7' => 'v1.7.sql',
'1.8' => 'v1.8.sql',
'2.0' => 'v2.0.sql',
'2.0.1' => 'v2.0.1.sql',
'2.1' => 'v2.1.sql',
];
$steps = [];
foreach ($allVersions as $version => $file) {
if (version_compare($version, $currentVersion, '>') &&
version_compare($version, $targetVersion, '<=')) {
$steps[] = ['version' => $version, 'file' => $file];
}
}
usort($steps, function($a, $b) { return version_compare($a['version'], $b['version']); });
echo json_encode([
'needs_upgrade' => $needsUpgrade,
'current' => $currentVersion,
'target' => $targetVersion,
'steps' => $steps
]);
} catch (PDOException $e) {
echo json_encode(['error' => '数据库连接失败: ' . $e->getMessage()]);
}
// 转发后端返回的升级数据
echo json_encode($result['data']);

View File

@@ -0,0 +1,89 @@
<?php
/**
* 执行单个升级步骤(代理至后端 API
*/
header('Content-Type: application/json; charset=utf-8');
require_once __DIR__ . '/../config.php';
// 验证登录和权限
if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'admin') {
http_response_code(401);
echo json_encode(['success' => false, 'error' => '未授权']);
exit();
}
$role = $_SESSION['role'] ?? '';
if ($role !== '班主任') {
http_response_code(403);
echo json_encode(['success' => false, 'error' => '权限不足']);
exit();
}
// 只接受 POST
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(400);
echo json_encode(['success' => false, 'error' => '无效请求']);
exit();
}
$stepVersion = $_GET['version'] ?? '';
if (empty($stepVersion)) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => '缺少版本号参数']);
exit();
}
// 从 session 获取 JWT token
$token = $_SESSION['jwt_token'] ?? '';
if (empty($token)) {
http_response_code(401);
echo json_encode(['success' => false, 'error' => '会话已过期,请重新登录']);
exit();
}
// 调用后端 API
$apiUrl = API_BASE_URL . '/api/upgrade/step';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $apiUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['version' => $stepVersion]),
CURLOPT_TIMEOUT => API_TIMEOUT,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json'
],
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 0
]);
$apiResponse = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200 || empty($apiResponse)) {
http_response_code(500);
echo json_encode([
'success' => false,
'version' => $stepVersion,
'error' => '无法连接升级服务'
]);
exit();
}
$result = json_decode($apiResponse, true);
if (!$result || !isset($result['success']) || !$result['success']) {
http_response_code(500);
echo json_encode([
'success' => false,
'version' => $stepVersion,
'error' => $result['message'] ?? '升级失败'
]);
exit();
}
// 转发后端返回的数据
echo json_encode($result['data']);

View File

@@ -160,6 +160,7 @@ $_SESSION['username'] = $data['username'];
$_SESSION['real_name'] = $data['real_name'] ?? '';
$_SESSION['role'] = $data['role'] ?? '';
$_SESSION['login_time'] = time();
$_SESSION['jwt_token'] = $token;
// 如果是学生,额外设置 student_id
if ($data['user_type'] === 'student') {

View File

@@ -132,13 +132,8 @@ function formatDateTime(dateStr) {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
}
function getStatusBadge(status, type = 'homework') {
function getStatusBadge(status, type = 'attendance') {
const statusMap = {
homework: {
'submitted': '已提交',
'not_submitted': '未提交',
'late': '迟交'
},
attendance: {
'present': '出勤',
'absent': '缺勤',
@@ -146,15 +141,13 @@ function getStatusBadge(status, type = 'homework') {
'leave': '请假'
}
};
const texts = statusMap[type] || statusMap.homework;
const texts = statusMap[type] || statusMap.attendance;
const text = texts[status] || status;
let className = 'status-badge ';
switch (status) {
case 'submitted':
case 'present':
className += 'status-submitted';
break;
case 'not_submitted':
case 'absent':
className += 'status-not_submitted';
break;

View File

@@ -118,11 +118,113 @@ async function exportMoralityRecords() {
console.error('导出失败:', err);
}
}
// 宿舍集体加分相关
var dormitoryStudentIds = [];
async function showDormitoryPointsModal() {
dormitoryStudentIds = [];
document.getElementById('dormitorySelect').innerHTML = '<option value="">-- 请选择宿舍 --</option>';
document.getElementById('dormitoryStudentsGroup').style.display = 'none';
document.getElementById('dormitoryStudentsList').innerHTML = '';
document.getElementById('dormitoryPointsChange').value = '';
document.getElementById('dormitoryPointsReason').value = '';
// 加载宿舍列表
const res = await apiGet('/api/admin/students/dormitories');
if (res && res.success && res.data.dormitories) {
const select = document.getElementById('dormitorySelect');
res.data.dormitories.forEach(d => {
const option = document.createElement('option');
option.value = d;
option.textContent = d;
select.appendChild(option);
});
}
document.getElementById('dormitoryPointsModal').style.display = 'flex';
}
async function onDormitorySelected() {
const dormitory = document.getElementById('dormitorySelect').value;
const studentsGroup = document.getElementById('dormitoryStudentsGroup');
const studentsList = document.getElementById('dormitoryStudentsList');
const studentsCount = document.getElementById('dormitoryStudentsCount');
dormitoryStudentIds = [];
studentsList.innerHTML = '';
if (!dormitory) {
studentsGroup.style.display = 'none';
return;
}
// 加载该宿舍的学生
const res = await apiGet('/api/admin/students', { dormitory_number: dormitory, page_size: 1000 });
if (res && res.success && res.data.students) {
const students = res.data.students;
if (students.length === 0) {
studentsList.innerHTML = '<p style="color: var(--text-secondary);">该宿舍暂无学生</p>';
studentsCount.textContent = '';
} else {
students.forEach(s => {
dormitoryStudentIds.push(s.student_id);
const div = document.createElement('div');
div.style.cssText = 'display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid var(--border-color);';
div.innerHTML = `<span>${escapeHtml(s.name)}</span><span style="color: var(--text-secondary);">${escapeHtml(s.student_no)}</span>`;
studentsList.appendChild(div);
});
studentsCount.textContent = `${students.length}`;
}
studentsGroup.style.display = 'block';
} else {
studentsList.innerHTML = '<p style="color: var(--text-secondary);">加载失败</p>';
studentsGroup.style.display = 'block';
}
}
async function submitDormitoryPoints() {
if (dormitoryStudentIds.length === 0) {
showToast('该宿舍没有学生', 'warning');
return;
}
const pointsChange = parseInt(document.getElementById('dormitoryPointsChange').value);
const reason = document.getElementById('dormitoryPointsReason').value;
if (isNaN(pointsChange) || pointsChange === 0) {
showToast('分值不能为0', 'error');
return;
}
if (!reason.trim()) {
showToast('请填写原因', 'error');
return;
}
const data = {
student_ids: dormitoryStudentIds,
points_change: pointsChange,
reason: reason
};
const res = await apiPost('/api/admin/conduct/add', data);
if (res && res.success) {
showToast(`操作成功: ${res.data.success_count} 人成功`);
closeModal('dormitoryPointsModal');
loadStudents();
} else {
showToast(res?.message || '操作失败', 'error');
}
}
loadStudents();
window.loadStudents = loadStudents;
window.showSinglePointsModal = showSinglePointsModal;
window.exportMoralityRecords = exportMoralityRecords;
window.showDormitoryPointsModal = showDormitoryPointsModal;
window.onDormitorySelected = onDormitorySelected;
window.submitDormitoryPoints = submitDormitoryPoints;
})();

View File

@@ -25,9 +25,15 @@ async function loadDashboard() {
}
let quickActions = '';
if (role === '班主任' || role === '班长' || role === '劳动委员' || role === '志愿委员') {
if (role === '班主任' || role === '班长' || role === '学习委员' || role === '劳动委员' || role === '志愿委员') {
quickActions += '<button class="btn btn-primary" onclick="location.href=\'/admin/conduct.php\'">操行分管理</button>';
}
if (role === '班主任' || role === '学习委员') {
quickActions += '<button class="btn btn-primary" onclick="location.href=\'/admin/homework.php\'">作业扣分</button>';
}
if (role === '班主任' || role === '考勤委员') {
quickActions += '<button class="btn btn-primary" onclick="location.href=\'/admin/attendance.php\'">考勤管理</button>';
}
if (role === '班主任') {
quickActions += '<button class="btn btn-outline" onclick="location.href=\'/admin/students.php\'">导入学生</button>';
quickActions += '<button class="btn btn-secondary" onclick="location.href=\'/admin/conduct.php\'">导出德育分记录</button>';

View File

@@ -40,7 +40,7 @@ async function loadHistory(page = 1) {
end_date: endDate
};
if (studentId) params.student_id = studentId;
if (relatedType && !isGrouped) params.related_type = relatedType;
if (relatedType) params.related_type = relatedType;
if (isGrouped) params.grouped = true;
const res = await apiGet('/api/admin/conduct/history', params);

View File

@@ -146,7 +146,7 @@
const students = data.students || [];
let html = '<h4>预览数据</h4><div class="table-wrapper"><table><thead><tr>';
html += '<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 => {
@@ -154,6 +154,7 @@
<td>${escapeHtml(s.student_no || '')}</td>
<td>${escapeHtml(s.name || '')}</td>
<td>${escapeHtml(s.parent_phone || '')}</td>
<td>${escapeHtml(s.dormitory_number || '-')}</td>
<td>${escapeHtml(s.password || '123456')}</td>
</tr>`;
});

View File

@@ -16,25 +16,18 @@ async function loadHomework() {
const res = await apiGet(`/api/student/homework/${STUDENT_ID}`);
if (res && res.success) {
let html = '';
res.data.homework.forEach(hw => {
// 提交状态
let statusDisplay = '-';
if (hw.status) {
statusDisplay = getStatusBadge(hw.status, 'homework');
}
// 扣分显示
const pointsDisplay = hw.points ? `<span class="text-danger">${hw.points}分</span>` : '-';
res.data.homework.forEach(record => {
const pointsClass = record.points_change > 0 ? 'plus' : 'minus';
const pointsColor = record.points_change > 0 ? '#38a169' : '#e53e3e';
html += `<tr>
<td>${escapeHtml(hw.title)}</td>
<td>${escapeHtml(hw.subject_name)}</td>
<td>${hw.deadline || '-'}</td>
<td>${statusDisplay}</td>
<td>${pointsDisplay}</td>
<td>${formatDateTime(record.created_at)}</td>
<td style="color: ${pointsColor}; font-weight: bold;">${record.points_change > 0 ? '+' : ''}${record.points_change}</td>
<td>${escapeHtml(record.reason)}</td>
<td>${escapeHtml(record.recorder_name || '-')}</td>
</tr>`;
});
if (res.data.homework.length === 0) {
html = '<tr><td colspan="5" style="text-align:center; padding: 40px; color: #999;">📝 暂无作业记录</td></tr>';
html = '<tr><td colspan="4" style="text-align:center; padding: 40px; color: #999;">📝 暂无作业扣分记录</td></tr>';
}
document.getElementById('homeworkList').innerHTML = html;
}

View File

@@ -1,102 +0,0 @@
/**
* 班级操行分管理系统 - 科目管理页JS
*
* 开发者: Canglan
* 版权归属: Sea Network Technology Studio
*
* 版权所有 © Sea Network Technology Studio
*/
(function() {
'use strict';
async function loadSubjects() {
const res = await apiGet('/api/subject/list');
if (res && res.success) {
let html = '';
res.data.subjects.forEach(sub => {
html += `
<div class="subject-item">
<span class="subject-name">${escapeHtml(sub.subject_name)}</span>
<span class="subject-code">${escapeHtml(sub.subject_code || '')}</span>
<span class="subject-status ${sub.is_active ? 'subject-status-active' : 'subject-status-inactive'}">
${sub.is_active ? '启用' : '禁用'}
</span>
<button class="btn btn-sm btn-outline" onclick="showEditSubjectModal(${sub.subject_id}, '${escapeHtml(sub.subject_name)}', '${escapeHtml(sub.subject_code || '')}', ${sub.sort_order || 0})">编辑</button>
<button class="btn btn-sm btn-ghost" onclick="toggleSubject(${sub.subject_id}, ${!sub.is_active})">
${sub.is_active ? '禁用' : '启用'}
</button>
<button class="btn btn-sm btn-outline-danger" onclick="deleteSubject(${sub.subject_id})">删除</button>
</div>
`;
});
if (res.data.subjects.length === 0) {
html = '<p style="text-align:center;padding:40px;">暂无科目,请点击"添加科目"</p>';
}
document.getElementById('subjectList').innerHTML = html;
}
}
async function toggleSubject(subjectId, enable) {
const res = await apiPut(`/api/subject/update/${subjectId}`, { is_active: enable });
if (res && res.success) {
showToast(enable ? '科目已启用' : '科目已禁用');
loadSubjects();
} else {
showToast(res?.message || '操作失败', 'error');
}
}
async function deleteSubject(subjectId) {
if (!confirm('确定要删除该科目吗?')) return;
const res = await apiDelete('/api/subject/delete/' + subjectId);
if (res && res.success) {
showToast('科目删除成功');
loadSubjects();
} else {
showToast(res?.message || '删除失败', 'error');
}
}
function showEditSubjectModal(subjectId, name, code, sortOrder) {
document.getElementById('editSubjectId').value = subjectId;
document.getElementById('editSubjectName').value = name;
document.getElementById('editSubjectCode').value = code;
document.getElementById('editSubjectSortOrder').value = sortOrder;
document.getElementById('editSubjectModal').style.display = 'flex';
}
async function submitEditSubject() {
const subjectId = document.getElementById('editSubjectId').value;
const subjectName = document.getElementById('editSubjectName').value.trim();
const subjectCode = document.getElementById('editSubjectCode').value.trim();
const sortOrder = document.getElementById('editSubjectSortOrder').value;
if (!subjectName) {
showToast('请填写科目名称', 'warning');
return;
}
const data = { subject_name: subjectName };
if (subjectCode) data.subject_code = subjectCode;
if (sortOrder !== '') data.sort_order = parseInt(sortOrder);
const res = await apiPut(`/api/subject/update/${subjectId}`, data);
if (res && res.success) {
showToast('科目更新成功');
closeModal('editSubjectModal');
loadSubjects();
} else {
showToast(res?.message || '更新失败', 'error');
}
}
loadSubjects();
window.loadSubjects = loadSubjects;
window.toggleSubject = toggleSubject;
window.deleteSubject = deleteSubject;
window.showEditSubjectModal = showEditSubjectModal;
window.submitEditSubject = submitEditSubject;
})();

View File

@@ -7,37 +7,42 @@
"_comment6": " student_no - 必填,学生学号,唯一标识",
"_comment7": " name - 必填,学生姓名",
"_comment8": " parent_phone - 可选家长手机号11位手机号",
"_comment9": " password - 可选,初始密码,不填则默认 123456",
"_comment10": "================================================",
"_comment11": "导入规则:",
"_comment12": " 1. 学生操行分初始值 = 60分",
"_comment13": " 2. 学生账号 = 学号,密码 = 指定的password或123456",
"_comment14": " 3. 家长账号 = 手机号若parent_phone有值,密码 = 指定的password或123456",
"_comment15": " 4. 家长姓名默认显示为 '学生姓名家长'",
"_comment16": "================================================",
"_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": ""
}
]

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 === '劳动委员' || $role === '志愿委员'): ?>
<?php if ($role === '班主任' || $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

@@ -42,10 +42,6 @@ include __DIR__ . '/../includes/header.php';
<div class="stat-label">班级排名</div>
<div class="stat-value" id="studentRank">--</div>
</div>
<div class="stat-card">
<div class="stat-label">缺交次数</div>
<div class="stat-value" id="homeworkMissing">--</div>
</div>
</div>
<div class="initial-points-hint" id="initialPointsHint"></div>
</div>
@@ -93,15 +89,6 @@ async function loadDashboard() {
}
}
// 加载作业缺交次数
const hwRes = await apiGet('/api/parent/child/homework');
if (hwRes && hwRes.success && hwRes.data.homework) {
const homework = hwRes.data.homework;
const total = homework.length;
const notSubmitted = homework.filter(h => h.status === 'not_submitted' || h.status === 'late').length;
document.getElementById('homeworkMissing').textContent = `缺交 ${notSubmitted}/${total} 次`;
}
// 显示初始分提示
const initialPoints = window.STUDENT_INITIAL_POINTS || 60;
document.getElementById('initialPointsHint').textContent = `初始操行分为 ${initialPoints} 分`;

View File

@@ -94,7 +94,7 @@ include __DIR__ . '/../includes/header.php';
<div class="stat-value" id="studentRank">--</div>
</div>
<div class="stat-card">
<div class="stat-label">缺交次数</div>
<div class="stat-label">作业扣分</div>
<div class="stat-value" id="homeworkRate">--</div>
</div>
<div class="stat-card">
@@ -140,11 +140,10 @@ include __DIR__ . '/../includes/header.php';
<table>
<thead>
<tr>
<th>科目</th>
<th>时间</th>
<th>分值</th>
<th>备注</th>
<th>作业</th>
<th>原因</th>
<th>操作人</th>
</tr>
</thead>
<tbody id="homeworkList"></tbody>
@@ -319,14 +318,13 @@ include __DIR__ . '/../includes/header.php';
document.getElementById('studentRank').textContent = '--';
}
}
// 获取作业统计 - 缺交次数
// 获取作业扣分统计
const homeworkRes = await apiGet(`/api/student/homework/${STUDENT_ID}`);
if (homeworkRes && homeworkRes.success) {
const stats = homeworkRes.data.statistics;
const notSubmitted = (stats.not_submitted || 0) + (stats.late || 0);
const deductions = stats.deductions || 0;
const total = stats.total || 0;
document.getElementById('homeworkRate').textContent = `缺交 ${notSubmitted}/${total} 次`;
}
document.getElementById('homeworkRate').textContent = `${deductions} 次扣分`;
}
// 获取考勤统计
@@ -394,25 +392,24 @@ include __DIR__ . '/../includes/header.php';
const res = await apiGet(`/api/student/homework/${STUDENT_ID}`);
if (res && res.success) {
let html = '';
res.data.homework.forEach(hw => {
const pointsDisplay = hw.points ? hw.points + '分' : '-';
res.data.homework.forEach(record => {
const pointsClass = record.points_change > 0 ? 'plus' : 'minus';
html += `
<tr>
<td>${escapeHtml(hw.subject_name)}</td>
<td>${hw.deadline || hw.created_at}</td>
<td>${pointsDisplay}</td>
<td>${escapeHtml(hw.comments || '-')}</td>
<td>${escapeHtml(hw.title)}</td>
<td>${formatDateTime(record.created_at)}</td>
<td class="record-points ${pointsClass}">${record.points_change > 0 ? '+' : ''}${record.points_change}</td>
<td>${escapeHtml(record.reason)}</td>
<td>${escapeHtml(record.recorder_name || '-')}</td>
</tr>
`;
});
if (res.data.homework.length === 0) {
html = '<tr><td colspan="5" style="text-align:center;">暂无作业</td></tr>';
html = '<tr><td colspan="4" style="text-align:center;">暂无作业扣分记录</td></tr>';
}
document.getElementById('homeworkList').innerHTML = html;
}
} catch (error) {
console.error('加载作业失败:', error);
console.error('加载作业记录失败:', error);
}
}

View File

@@ -36,7 +36,7 @@ include __DIR__ . '/../includes/header.php';
<div class="table-wrapper">
<table class="table">
<thead>
<tr><th>作业名称</th><th>科目</th><th>截止时间</th><th>提交状态</th><th>扣分</th></tr>
<tr><th>时间</th><th>分值</th><th>原因</th><th>操作人</th></tr>
</thead>
<tbody id="homeworkList"></tbody>
</table>