/** * 班级操行分管理系统 - 公共JS * * 开发者: Canglan * 联系方式: admin@sea-studio.top * 版权归属: Sea Network Technology Studio * 许可证: MIT License * * 版权所有 © Sea Network Technology Studio */ function getToken() { return localStorage.getItem(window.JWT_STORAGE_KEY || 'class_system_token'); } function getUserInfo() { const userStr = localStorage.getItem(window.USER_STORAGE_KEY || 'class_system_user'); if (!userStr) return null; try { return JSON.parse(userStr); } catch { return null; } } function setUserInfo(user) { localStorage.setItem(window.USER_STORAGE_KEY || 'class_system_user', JSON.stringify(user)); } function clearAuth() { localStorage.removeItem(window.JWT_STORAGE_KEY || 'class_system_token'); localStorage.removeItem(window.USER_STORAGE_KEY || 'class_system_user'); } async function apiRequest(url, options = {}) { const token = getToken(); const headers = { 'Content-Type': 'application/json', ...options.headers }; if (token) { headers['Authorization'] = `Bearer ${token}`; } const baseUrl = window.API_BASE_URL; const fullUrl = `${baseUrl}${url}`; try { const response = await fetch(fullUrl, { ...options, headers }); const data = await response.json(); if (response.status === 401) { clearAuth(); // 同步清除 PHP Session,防止 index.php 302 重定向循环 try { await fetch('/api/clear_session.php', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); } catch (e) { console.warn('[Auth] 清除PHP Session失败:', e); } // 防循环机制:检查是否已在登录页 if (window.location.pathname === '/index.php' || window.location.pathname === '/') { console.warn('[Auth] 已在登录页收到401,停止重定向'); return null; } // 防循环机制:5秒内重复401则停止重定向 const now = Date.now(); const lastRedirect = parseInt(sessionStorage.getItem('_last_401_redirect') || '0'); if (now - lastRedirect < 5000) { console.warn('[Auth] 5秒内重复401,停止重定向。请检查Token是否有效。'); return null; } sessionStorage.setItem('_last_401_redirect', now.toString()); window.location.href = '/index.php'; return null; } return data; } catch (error) { console.error('API请求错误:', error); showToast('网络错误,请稍后重试', 'error'); return null; } } function apiGet(url, params = {}) { const queryString = new URLSearchParams(params).toString(); const fullUrl = queryString ? `${url}?${queryString}` : url; return apiRequest(fullUrl, { method: 'GET' }); } function apiPost(url, data = {}) { return apiRequest(url, { method: 'POST', body: JSON.stringify(data) }); } function apiPut(url, data = {}) { return apiRequest(url, { method: 'PUT', body: JSON.stringify(data) }); } function apiDelete(url) { return apiRequest(url, { method: 'DELETE' }); } function showToast(message, type = 'success') { const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => toast.remove(), 3000); } function formatDate(dateStr) { if (!dateStr) return '-'; const date = new Date(dateStr); return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; } function formatDateTime(dateStr) { if (!dateStr) return '-'; const date = new Date(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') { const statusMap = { homework: { 'submitted': '已提交', 'not_submitted': '未提交', 'late': '迟交' }, attendance: { 'present': '出勤', 'absent': '缺勤', 'late': '迟到', 'leave': '请假' } }; const texts = statusMap[type] || statusMap.homework; 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; case 'late': className += 'status-late'; break; case 'leave': className += 'status-leave'; break; default: className += 'status-not_submitted'; } return `${text}`; } async function logout() { // 清除 PHP Session try { await fetch('/api/clear_session.php', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); } catch (e) { console.warn('清除Session失败', e); } // 清除后端 Token try { await apiPost('/api/auth/logout'); } catch (e) { console.warn('后端登出失败', e); } // 清除 localStorage clearAuth(); // 跳转回登录页 window.location.href = '/index.php'; } function escapeHtml(str) { if (!str) return ''; return str.replace(/[&<>]/g, function(m) { if (m === '&') return '&'; if (m === '<') return '<'; if (m === '>') return '>'; return m; }); } /** * 智能分页渲染(最多显示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 += `« 上一页`; } if (totalPages <= MAX_VISIBLE) { // 总页数不超过最大显示数,全部显示 for (let i = 1; i <= totalPages; i++) { if (i === currentPage) { html += `${i}`; } else { html += `${i}`; } } } else { // 需要省略号 // 始终显示第1页 if (currentPage === 1) { html += `1`; } else { html += `1`; } // 计算中间页码范围 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 += `...`; } // 中间页码 for (let i = start; i <= end; i++) { if (i === currentPage) { html += `${i}`; } else { html += `${i}`; } } // 后省略号 if (end < totalPages - 1) { html += `...`; } // 始终显示最后一页 if (currentPage === totalPages) { html += `${totalPages}`; } else { html += `${totalPages}`; } } // 下一页按钮 if (currentPage < totalPages) { html += `下一页 »`; } // 页码跳转 html += `跳至 / ${totalPages}页`; 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'); if (userNameSpan && user) { userNameSpan.textContent = user.real_name || user.username; } const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) { logoutBtn.addEventListener('click', logout); } });