v2.2更新
This commit is contained in:
@@ -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')">×</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'; ?>
|
||||
@@ -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) {
|
||||
|
||||
@@ -54,7 +54,7 @@ include __DIR__ . '/../includes/header.php';
|
||||
<div class="filter-group" style="min-width:auto;">
|
||||
<label> </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 === '班主任'): ?>
|
||||
|
||||
@@ -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']);
|
||||
|
||||
89
frontend/api/execute_upgrade.php
Normal file
89
frontend/api/execute_upgrade.php
Normal 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']);
|
||||
@@ -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') {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
})();
|
||||
|
||||
@@ -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>';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>`;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
})();
|
||||
@@ -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": ""
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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 === '学习委员'): ?>
|
||||
|
||||
@@ -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} 分`;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user