document.addEventListener('DOMContentLoaded', function () { var currentUserName = document.getElementById('currentUserName'); var logoutBtn = document.getElementById('logoutBtn'); var sidebarNav = document.getElementById('sidebarNav'); var sectionTitle = document.getElementById('sectionTitle'); var sectionDesc = document.getElementById('sectionDesc'); var subTabs = document.getElementById('subTabs'); var mainView = document.getElementById('mainView'); var state = { user: null, currentView: '', currentTab: 'all', events: [], myEvents: [], adminUsers: [], allAdminUsers: [], adminRegistrations: [], adminEvents: [], dashboardStats: { onlineUsers: 0 }, editingEventId: null, editingUserId: null, userPage: 1, userPageSize: 8, userKeyword: '', userTotal: 0, eventPage: 1, eventPageSize: 8, eventTotal: 0, adminEventPage: 1, adminEventPageSize: 8, adminEventTotal: 0, athletePage: 1, athletePageSize: 8, athleteTotal: 0 }; var genderOptions = ['男', '女']; var collegeOptions = [ '文学院与文化传播学院', '马克思主义学院', '教育学院', '外国语学院', '历史文化学院', '商学院', '化学工程与技术学院', '电子信息与电气工程学院', '数学与统计学院', '生物工程与技术学院', '机电工程学院', '土木工程学院', '资源与环境工程学院', '体育学院', '美术与设计学院', '音乐舞蹈学院', '卫生健康学院', '继续教育学院(培训中心)' ]; var categoryOptions = ['青年组', '老年组']; var studentMenus = [ { key: 'profile-view', label: '查看信息', desc: '查看当前账号信息。' }, { key: 'profile-edit', label: '修改信息', desc: '修改个人资料。' }, { key: 'events', label: '运动会报名', desc: '查看项目、报名和取消报名。' } ]; var adminMenus = [ { key: 'admin-home', label: '运动会管理', desc: '查看系统概览。' }, { label: '用户信息管理', children: [ { key: 'user-list', label: '用户列表', desc: '查询、导出、重置密码及禁用等操作。' }, { key: 'user-add', label: '新增用户', desc: '新增系统用户或批量上传。' } ] }, { key: 'team-info', label: '团体信息管理', desc: '管理学院、班级等团体信息。' }, { label: '项目管理', children: [ { key: 'event-list', label: '项目列表', desc: '查看、编辑和删除比赛项目。' }, { key: 'event-add', label: '新增项目', desc: '新增比赛项目。' } ] }, { key: 'athlete-manage', label: '参赛运动员管理', desc: '查看所有已报名人员。' }, { key: 'score-manage', label: '参赛成绩管理', desc: '录入和维护成绩。' }, { key: 'record-manage', label: '项目记录管理', desc: '维护项目记录。' }, { key: 'system-manage', label: '系统管理', desc: '维护系统配置。' } ]; function escapeHtml(value) { return String(value == null ? '' : value) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function getMenus() { return state.user && state.user.role === 'ADMIN' ? adminMenus : studentMenus; } function getMeta(viewKey) { var menus = getMenus(); for (var i = 0; i < menus.length; i++) { var item = menus[i]; if (item.key === viewKey) return item; if (item.children) { for (var j = 0; j < item.children.length; j++) { if (item.children[j].key === viewKey) return item.children[j]; } } } return { label: '系统首页', desc: '欢迎进入运动会报名系统。' }; } function isParentActive(item) { if (item.key === state.currentView) return true; if (item.children) { for (var i = 0; i < item.children.length; i++) { if (item.children[i].key === state.currentView) return true; } } return false; } function renderSidebar() { var expandedParent = null; var menus = getMenus(); for (var i = 0; i < menus.length; i++) { if (menus[i].children && isParentActive(menus[i])) { expandedParent = menus[i].label; break; } } var html = ''; for (var p = 0; p < menus.length; p++) { var item = menus[p]; if (item.children) { var isExpanded = (item.label === expandedParent); html += ''; } else { html += ''; } } sidebarNav.innerHTML = html; Array.prototype.slice.call(sidebarNav.querySelectorAll('.nav-item[data-view]')).forEach(function (btn) { btn.addEventListener('click', function () { switchView(btn.getAttribute('data-view')); }); }); Array.prototype.slice.call(sidebarNav.querySelectorAll('.nav-parent')).forEach(function (btn) { btn.addEventListener('click', function () { var parentLabel = btn.getAttribute('data-parent'); var childGroup = sidebarNav.querySelector('[data-parent-group="' + CSS.escape(parentLabel) + '"]'); if (childGroup) { var isOpen = childGroup.classList.contains('open'); Array.prototype.slice.call(sidebarNav.querySelectorAll('.nav-children.open')).forEach(function (g) { g.classList.remove('open'); }); Array.prototype.slice.call(sidebarNav.querySelectorAll('.nav-parent.expanded')).forEach(function (p) { p.classList.remove('expanded'); }); if (!isOpen) { childGroup.classList.add('open'); btn.classList.add('expanded'); } } }); }); } function renderTabs() { if (state.currentView !== 'events') { subTabs.innerHTML = ''; subTabs.classList.add('hidden'); return; } subTabs.innerHTML = '' + '' + ''; subTabs.classList.remove('hidden'); Array.prototype.slice.call(subTabs.querySelectorAll('.tab-item')).forEach(function (button) { button.addEventListener('click', function () { state.currentTab = button.getAttribute('data-tab'); renderTabs(); renderCurrentView(); }); }); } // ===================== 查看信息(独立页面) ===================== function renderProfileView() { mainView.innerHTML = '' + '
' + '

基础资料

当前账号信息如下。

' + '
' + infoCard('身份证号', state.user.idCard) + infoCard('登录账号', state.user.username) + infoCard('姓名', state.user.name) + infoCard('联系电话', state.user.phone) + infoCard('性别', state.user.gender) + infoCard('学院', state.user.college) + infoCard('班级', state.user.className) + infoCard('学号', state.user.studentNo) + infoCard('类别', state.user.category) + infoCard('角色', state.user.role) + '
' + '
'; } // ===================== 修改信息(独立页面) ===================== function renderProfileEdit() { mainView.innerHTML = '' + '
' + '

修改个人信息

可在此更新姓名、电话、学院、班级、学号等信息。

' + '
' + fieldInput('姓名', 'name', state.user.name, '请输入姓名') + fieldInput('联系电话', 'phone', state.user.phone, '请输入电话') + fieldSelect('性别', 'gender', state.user.gender, genderOptions, '请选择性别') + fieldSelect('学院', 'college', state.user.college, collegeOptions, '请选择学院') + fieldInput('班级', 'className', state.user.className, '请输入班级') + fieldInput('学号', 'studentNo', state.user.studentNo, '请输入学号') + fieldSelect('类别', 'category', state.user.category, categoryOptions, '请选择类别', true) + '
' + ' ' + ' ' + '
' + '

' + '
' + '
'; var form = document.getElementById('profileForm'); var messageEl = document.getElementById('profileMessage'); var cancelBtn = document.getElementById('cancelProfileEdit'); form.addEventListener('submit', function (event) { event.preventDefault(); var formData = new FormData(form); appUtils.ajax({ method: 'PUT', url: '/api/users/me', data: { name: String(formData.get('name') || '').trim(), phone: String(formData.get('phone') || '').trim(), gender: String(formData.get('gender') || '').trim(), college: String(formData.get('college') || '').trim(), className: String(formData.get('className') || '').trim(), studentNo: String(formData.get('studentNo') || '').trim(), category: String(formData.get('category') || '').trim() }, success: function (response) { if (!response.success || !response.data) { appUtils.showMessage(messageEl, response.message || '保存失败', false); return; } state.user = response.data; updateCurrentUserName(); appUtils.showMessage(messageEl, '个人信息修改成功', true); setTimeout(function () { switchView('profile-view'); }, 800); }, error: function (xhr, response) { appUtils.showMessage(messageEl, (response && response.message) || '保存失败', false); } }); }); if (cancelBtn) { cancelBtn.addEventListener('click', function () { switchView('profile-view'); }); } } // ===================== 运动会报名 ===================== function renderEventTable(list, isMine) { state.eventTotal = list.length; var totalPages = Math.ceil(state.eventTotal / state.eventPageSize) || 1; if (state.eventPage > totalPages) state.eventPage = totalPages; var start = (state.eventPage - 1) * state.eventPageSize; var pageItems = list.slice(start, start + state.eventPageSize); if (!list.length) { mainView.innerHTML = '

暂无数据

' + (isMine ? '你还没有报名任何项目。' : '当前暂无可展示项目。') + '

'; return; } mainView.innerHTML = '' + '' + ' ' + ' ' + pageItems.map(function (item) { var buttonHtml = isMine ? '' : ''; return '' + '' + '' + '' + '' + '' + '' + '' + ''; }).join('') + ' ' + '
项目名称项目分类比赛时间比赛地点报名情况项目说明操作
' + escapeHtml(item.eventName) + '' + escapeHtml(item.eventCategory) + '' + escapeHtml(item.eventTime) + '' + escapeHtml(item.location) + '' + escapeHtml((item.registeredCount || 0) + '/' + (item.quota || 0)) + '' + escapeHtml(item.description) + '' + buttonHtml + '
' + renderEventPagination(totalPages); bindEventTableActions(); } function renderEventPagination(totalPages) { var html = ''; html += '

共 ' + state.eventTotal + ' 条记录,第 ' + state.eventPage + ' / ' + totalPages + ' 页

'; return html; } function bindEventTableActions() { Array.prototype.slice.call(document.querySelectorAll('.register-btn:not([disabled])')).forEach(function (button) { button.addEventListener('click', function () { var eventId = button.getAttribute('data-id'); appUtils.ajax({ method: 'POST', url: '/api/events/' + eventId + '/register', success: function (response) { if (!response.success) { window.alert(response.message || '报名失败'); return; } loadEventData(); }, error: function (xhr, response) { window.alert((response && response.message) || '报名失败'); } }); }); }); Array.prototype.slice.call(document.querySelectorAll('.cancel-btn')).forEach(function (button) { button.addEventListener('click', function () { var eventId = button.getAttribute('data-id'); appUtils.ajax({ method: 'DELETE', url: '/api/events/' + eventId + '/register', success: function (response) { if (!response.success) { window.alert(response.message || '取消报名失败'); return; } loadEventData(); }, error: function (xhr, response) { window.alert((response && response.message) || '取消报名失败'); } }); }); }); bindEventPaginationActions(); } function bindEventPaginationActions() { Array.prototype.slice.call(document.querySelectorAll('.page-btn[data-type="event"]:not([disabled])')).forEach(function (button) { button.addEventListener('click', function () { var page = Number(button.getAttribute('data-page')); if (page >= 1) { state.eventPage = page; renderCurrentView(); } }); }); } // ===================== 管理员首页 ===================== function renderAdminHome() { var totalUsers = state.allAdminUsers.length; var totalRegistrations = state.adminRegistrations.length; var totalEvents = state.adminEvents.length; var activeUsers = state.allAdminUsers.filter(function (item) { return item.status !== 'DISABLED'; }).length; var disabledUsers = totalUsers - activeUsers; var avgFillRate = totalEvents ? Math.round(state.adminEvents.reduce(function (sum, item) { var quota = Number(item.quota || 0); var registered = Number(item.registeredCount || 0); return sum + (quota > 0 ? Math.min(registered / quota, 1) : 0); }, 0) / totalEvents * 100) : 0; var onlineUsers = Number((state.dashboardStats && state.dashboardStats.onlineUsers) || 0); var collegeStats = buildCountStats(state.adminRegistrations, function (item) { return item.college; }); var categoryStats = buildCountStats(state.adminEvents, function (item) { return item.eventCategory; }); var compactCategoryStats = compressStats(categoryStats, 4, '其他分类'); var statusStats = [ { label: '正常用户', count: activeUsers, color: '#0f766e' }, { label: '禁用用户', count: Math.max(disabledUsers, 0), color: '#f97316' } ].filter(function (item) { return item.count > 0; }); var latestRegistrations = state.adminRegistrations .slice() .sort(function (a, b) { return parseDateValue(b.createdAt) - parseDateValue(a.createdAt); }) .slice(0, 4); var upcomingEvents = state.adminEvents .slice() .sort(function (a, b) { return parseDateValue(a.eventTime) - parseDateValue(b.eventTime); }) .filter(function (item) { return parseDateValue(item.eventTime) > 0; }) .slice(0, 4); var hotEvents = state.adminEvents .slice() .sort(function (a, b) { return Number(b.registeredCount || 0) - Number(a.registeredCount || 0); }) .slice(0, 4); mainView.innerHTML = '' + '
' + '
' + '
' + '
' + '

Sports Meet Admin

' + '

运动会管理仪表盘

' + '

查看系统运行概况、项目热度和最新报名动态。

' + '
' + ' ' + ' ' + ' ' + '
' + '
' + renderDonutPanel('用户状态分布', totalUsers, statusStats, '账号状态') + '
' + '
' + renderCategoryOverviewPanel('项目分类概览', compactCategoryStats) + '
' + '
' + '
' + dashboardMetricCard('实时在线', onlineUsers, onlineUsers > 0 ? '当前会话在线用户数' : '当前暂无在线用户') + dashboardMetricCard('用户总数', totalUsers, activeUsers + ' 正常 / ' + disabledUsers + ' 禁用') + dashboardMetricCard('报名记录', totalRegistrations, totalEvents ? '覆盖 ' + totalEvents + ' 个比赛项目' : '暂无比赛项目') + dashboardMetricCard('项目总数', totalEvents, categoryStats.length + ' 个项目分类') + dashboardMetricCard('平均满额率', avgFillRate + '%', '便于掌握项目热度与容量') + '
' + '
' + '
' + '

热门项目排行

按报名人数排序,突出最受关注项目。

' + renderCompactEventRanking(hotEvents, '当前没有项目数据') + '
' + '
' + '

最新动态

把报名时间和即将开始的项目压缩展示。

' + renderCompactFeed(latestRegistrations, upcomingEvents) + '
' + '
' + '
'; bindDashboardActions(); } function dashboardMetricCard(label, value, hint) { return '' + '
' + ' ' + escapeHtml(label) + '' + ' ' + escapeHtml(value) + '' + '

' + escapeHtml(hint) + '

' + '
'; } function dashboardStatRow(label, value) { return '' + '
' + ' ' + escapeHtml(label) + '' + ' ' + escapeHtml(value) + '' + '
'; } function renderDonutPanel(title, total, items, centerLabel) { return '' + '
' + '

' + escapeHtml(title) + '

当前数据分布概览。

' + renderDonutChart(items, total, centerLabel) + '
'; } function renderCategoryOverviewPanel(title, items) { return '' + '
' + '

' + escapeHtml(title) + '

按项目分类查看当前数量分布。

' + renderCategoryOverview(items) + '
'; } function buildCountStats(list, getter) { var map = {}; list.forEach(function (item) { var key = String(getter(item) || '').trim(); if (!key) { key = '未分类'; } map[key] = (map[key] || 0) + 1; }); return Object.keys(map).map(function (key) { return { label: key, count: map[key] }; }).sort(function (a, b) { return b.count - a.count; }); } function compressStats(items, maxItems, otherLabel) { if (!items.length || items.length <= maxItems) { return items; } var kept = items.slice(0, maxItems - 1); var rest = items.slice(maxItems - 1); var restCount = rest.reduce(function (sum, item) { return sum + Number(item.count || 0); }, 0); kept.push({ label: otherLabel || '其他', count: restCount, color: '#94a3b8' }); return kept; } function parseDateValue(value) { var text = String(value || '').trim(); if (!text) { return 0; } var normalized = text.replace(/-/g, '/'); var time = new Date(normalized).getTime(); return isNaN(time) ? 0 : time; } function renderMiniBars(items, emptyText) { if (!items.length) { return '
' + escapeHtml(emptyText) + '
'; } var max = items[0].count || 1; return '
' + items.map(function (item) { var width = Math.max(14, Math.round((item.count / max) * 100)); return '' + '
' + '
' + escapeHtml(item.label) + '' + escapeHtml(item.count) + '
' + '
' + '
'; }).join('') + '
'; } function renderRegistrationTimeline(list) { if (!list.length) { return '
当前还没有报名记录
'; } return '
' + list.map(function (item) { return '' + '
' + '
' + '
' + ' ' + escapeHtml(item.studentName) + ' 报名了 ' + escapeHtml(item.eventName) + '' + '

' + escapeHtml((item.college || '未填写学院') + ' · ' + (item.eventCategory || '未分类')) + '

' + ' ' + escapeHtml(item.createdAt || '时间未知') + '' + '
' + '
'; }).join('') + '
'; } function renderDonutChart(items, total, centerLabel) { if (!items.length || total <= 0) { return '
当前没有可统计的数据
'; } var palette = ['#0f766e', '#2563eb', '#f97316', '#e11d48', '#14b8a6', '#8b5cf6']; var current = 0; var gradientParts = []; var legendItems = []; items.forEach(function (item, index) { var count = Number(item.count || 0); if (count <= 0) { return; } var color = item.color || palette[index % palette.length]; var percent = total > 0 ? (count / total) * 100 : 0; var start = current; var end = current + percent; gradientParts.push(color + ' ' + start + '% ' + end + '%'); current = end; legendItems.push({ label: item.label, count: count, color: color, percent: Math.round(percent) }); }); return '' + '
' + '
' + '
' + ' ' + escapeHtml(total) + '' + ' ' + escapeHtml(centerLabel || '总计') + '' + '
' + '
' + '
' + legendItems.map(function (item) { return '' + '
' + ' ' + '
' + escapeHtml(item.label) + '

' + escapeHtml(item.count + ' · ' + item.percent + '%') + '

' + '
'; }).join('') + '
' + '
'; } function renderCategoryOverview(items) { if (!items.length) { return '
当前没有可统计的数据
'; } var max = Math.max.apply(null, items.map(function (item) { return Number(item.count || 0); })); return '
' + items.map(function (item, index) { var count = Number(item.count || 0); var width = max > 0 ? Math.max(12, Math.round((count / max) * 100)) : 0; return '' + '
' + '
' + ' ' + escapeHtml(item.label) + '' + ' ' + escapeHtml(count + ' 个项目') + '' + '
' + '
' + '
'; }).join('') + '
'; } function renderEventRanking(list, emptyText) { if (!list.length) { return '
' + escapeHtml(emptyText) + '
'; } var max = Number(list[0].registeredCount || 0) || 1; return '
' + list.map(function (item, index) { var registered = Number(item.registeredCount || 0); var quota = Number(item.quota || 0); var width = Math.max(10, Math.round((registered / max) * 100)); return '' + '
' + '
TOP ' + (index + 1) + '' + escapeHtml(item.eventName) + '
' + '

' + escapeHtml((item.eventCategory || '未分类') + ' · ' + (item.location || '地点待定')) + '

' + '
' + ' ' + escapeHtml(registered + ' / ' + quota + ' 人') + '' + '
'; }).join('') + '
'; } function renderCompactEventRanking(list, emptyText) { if (!list.length) { return '
' + escapeHtml(emptyText) + '
'; } return '
' + list.map(function (item, index) { var registered = Number(item.registeredCount || 0); var quota = Number(item.quota || 0); return '' + '
' + ' ' + (index + 1) + '' + '
' + ' ' + escapeHtml(item.eventName) + '' + '

' + escapeHtml((item.eventCategory || '未分类') + ' · ' + registered + '/' + quota + ' 人') + '

' + '
' + ' ' + escapeHtml(item.location || '地点待定') + '' + '
'; }).join('') + '
'; } function renderUpcomingEvents(list) { if (!list.length) { return '
当前没有可展示的比赛时间数据
'; } return '
' + list.map(function (item) { return '' + '
' + ' ' + escapeHtml(item.eventName) + '' + '

' + escapeHtml(item.eventCategory || '未分类') + '

' + ' ' + escapeHtml((item.eventTime || '时间待定') + ' · ' + (item.location || '地点待定')) + '' + '
'; }).join('') + '
'; } function renderCompactFeed(registrations, events) { var feedItems = []; registrations.forEach(function (item) { feedItems.push({ time: item.createdAt || '', title: (item.studentName || '有用户') + ' 报名了 ' + (item.eventName || '项目'), desc: (item.college || '未填写学院') + ' · ' + (item.eventCategory || '未分类'), type: '报名' }); }); events.forEach(function (item) { feedItems.push({ time: item.eventTime || '', title: (item.eventName || '项目') + ' 即将开始', desc: (item.location || '地点待定') + ' · ' + (item.eventCategory || '未分类'), type: '赛程' }); }); feedItems = feedItems .sort(function (a, b) { return parseDateValue(b.time) - parseDateValue(a.time); }) .slice(0, 6); if (!feedItems.length) { return '
当前没有最新动态
'; } return '
' + feedItems.map(function (item) { return '' + '
' + ' ' + escapeHtml(item.type) + '' + '
' + ' ' + escapeHtml(item.title) + '' + '

' + escapeHtml(item.desc) + '

' + '
' + ' ' + escapeHtml(item.time || '时间待定') + '' + '
'; }).join('') + '
'; } function bindDashboardActions() { Array.prototype.slice.call(document.querySelectorAll('.dashboard-link')).forEach(function (button) { button.addEventListener('click', function () { switchView(button.getAttribute('data-view')); }); }); } // ===================== 用户列表 ===================== function getPagedUsers() { var filtered = state.allAdminUsers; if (state.userKeyword) { var kw = state.userKeyword.toLowerCase(); filtered = state.allAdminUsers.filter(function (u) { return (u.name && u.name.toLowerCase().indexOf(kw) >= 0) || (u.username && u.username.toLowerCase().indexOf(kw) >= 0) || (u.studentNo && u.studentNo.toLowerCase().indexOf(kw) >= 0) || (u.college && u.college.toLowerCase().indexOf(kw) >= 0) || (u.idCard && u.idCard.toLowerCase().indexOf(kw) >= 0); }); } state.userTotal = filtered.length; var start = (state.userPage - 1) * state.userPageSize; return filtered.slice(start, start + state.userPageSize); } function renderPagination() { var totalPages = Math.ceil(state.userTotal / state.userPageSize) || 1; var html = ''; html += '

共 ' + state.userTotal + ' 条记录,第 ' + state.userPage + ' / ' + totalPages + ' 页

'; return html; } function renderUserList() { var pageUsers = getPagedUsers(); mainView.innerHTML = '' + '
' + '

用户列表

支持搜索、分页,以及重置密码、禁用/解除禁用和删除操作。

' + '
' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '
' + '
' + '
' + (pageUsers.length ? '' + '' + ' ' + ' ' + pageUsers.map(function (item) { var isAdmin = item.role === 'ADMIN'; var statusText = item.status === 'DISABLED' ? '已禁用' : '正常'; var statusClass = item.status === 'DISABLED' ? 'status-disabled' : 'status-active'; var actionBtns = ''; actionBtns += ''; actionBtns += ''; if (!isAdmin) { if (item.status === 'DISABLED') { actionBtns += ''; } else { actionBtns += ''; } actionBtns += ''; } return '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + ''; }).join('') + ' ' + '
姓名账号身份证号电话性别学院班级学号类别角色状态操作
' + escapeHtml(item.name) + '' + escapeHtml(item.username) + '' + escapeHtml(item.idCard) + '' + escapeHtml(item.phone) + '' + escapeHtml(item.gender) + '' + escapeHtml(item.college) + '' + escapeHtml(item.className) + '' + escapeHtml(item.studentNo) + '' + escapeHtml(item.category) + '' + escapeHtml(item.role) + '' + escapeHtml(statusText) + '' + actionBtns + '
' + renderPagination() : '

暂无用户

当前系统中还没有用户数据,或搜索结果为空。

') + '
'; bindUserListActions(); bindPaginationActions(); } function bindUserListActions() { var searchInput = document.getElementById('userSearchInput'); var searchBtn = document.getElementById('userSearchBtn'); var clearSearchBtn = document.getElementById('clearSearchBtn'); var exportBtn = document.getElementById('exportUsersBtn'); if (searchBtn) { searchBtn.addEventListener('click', function () { state.userKeyword = String(searchInput ? searchInput.value : '').trim(); state.userPage = 1; renderCurrentView(); }); } if (clearSearchBtn) { clearSearchBtn.addEventListener('click', function () { state.userKeyword = ''; state.userPage = 1; if (searchInput) { searchInput.value = ''; } renderCurrentView(); }); } if (searchInput) { searchInput.addEventListener('keydown', function (e) { if (e.key === 'Enter') { state.userKeyword = String(searchInput.value).trim(); state.userPage = 1; renderCurrentView(); } }); } // 导出用户 — 前端生成 CSV 下载 if (exportBtn) { exportBtn.addEventListener('click', function () { var exportData = state.userKeyword ? getPagedUsers() : state.allAdminUsers; if (!exportData.length) { window.alert('没有可导出的用户数据'); return; } var headers = ['姓名', '账号', '身份证号', '电话', '性别', '学院', '班级', '学号', '类别', '角色', '状态']; var csvContent = '\uFEFF' + headers.join(',') + '\n'; exportData.forEach(function (u) { var row = [ u.name || '', u.username || '', u.idCard || '', u.phone || '', u.gender || '', u.college || '', u.className || '', u.studentNo || '', u.category || '', u.role || '', u.status || '' ]; csvContent += row.map(function (v) { return '"' + String(v).replace(/"/g, '""') + '"'; }).join(',') + '\n'; }); var blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); var url = window.URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = '用户列表.csv'; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); }); } bindAdminUserActions(); } // ===================== 新增用户(独立页面) ===================== function renderUserAdd() { var editingUser = null; if (state.editingUserId) { editingUser = state.allAdminUsers.find(function (u) { return u.id === state.editingUserId; }) || null; } var isEditing = !!editingUser; mainView.innerHTML = '' + '
' + '

' + (isEditing ? '编辑用户信息' : '新增用户') + '

' + (isEditing ? '修改现有用户的基本信息。' : '管理员可在此新增系统用户。') + '

' + '
' + fieldInput('身份证号', 'idCard', isEditing ? editingUser.idCard : '', '请输入身份证号') + fieldInput('邮箱', 'email', isEditing ? editingUser.username : '', '请输入邮箱地址') + (!isEditing ? fieldInput('密码', 'password', '', '请输入密码(不少于6位)') + fieldInput('确认密码', 'confirmPassword', '', '请再次输入密码') : '') + fieldInput('姓名', 'name', isEditing ? editingUser.name : '', '请输入姓名') + fieldInput('联系电话', 'phone', isEditing ? editingUser.phone : '', '请输入电话') + fieldSelect('性别', 'gender', isEditing ? editingUser.gender : '', genderOptions, '请选择性别') + fieldSelect('学院', 'college', isEditing ? editingUser.college : '', collegeOptions, '请选择学院') + fieldInput('班级', 'className', isEditing ? editingUser.className : '', '请输入班级') + fieldInput('学号', 'studentNo', isEditing ? editingUser.studentNo : '', '请输入学号') + fieldSelect('类别', 'category', isEditing ? editingUser.category : '', categoryOptions, '请选择类别') + '
' + ' ' + ' ' + '
' + '

' + '
' + '
' + (!isEditing ? '' // 批量上传区域(仅新增时显示) + '
' + '

批量上传用户

通过 Excel 或 CSV 文件批量导入用户信息。

' + '
' + '
' + '

支持格式:.csv

' + '

📝 文件要求(列顺序固定):

' + '

身份证号 | 邮箱 | 密码 | 姓名 | 电话 | 性别 | 学院 | 班级 | 学号 | 类别

' + '

第一行为表头,数据从第二行开始。性别:男/女,类别:青年组/老年组。

' + '

注意:当前不支持直接上传 Excel,也不要直接把用户导出文件回传导入。

' + '
' + '
' + ' ' + ' ' + ' ' + '
' + '

' + '
' + '
' : ''); bindUserAddActions(); } function bindUserAddActions() { var form = document.getElementById('userForm'); var clearBtn = document.getElementById('clearUserForm'); var messageEl = document.getElementById('userFormMessage'); var uploadBtn = document.getElementById('uploadUsersBtn'); var fileInput = document.getElementById('uploadFileInput'); var fileNameEl = document.getElementById('uploadFileName'); var uploadMsgEl = document.getElementById('uploadMessage'); var isEditing = !!state.editingUserId; if (form) { form.addEventListener('submit', function (event) { event.preventDefault(); var formData = new FormData(form); var payload = { idCard: String(formData.get('idCard') || '').trim(), email: String(formData.get('email') || '').trim(), name: String(formData.get('name') || '').trim(), phone: String(formData.get('phone') || '').trim(), gender: String(formData.get('gender') || '').trim(), college: String(formData.get('college') || '').trim(), className: String(formData.get('className') || '').trim(), studentNo: String(formData.get('studentNo') || '').trim(), category: String(formData.get('category') || '').trim() }; if (!isEditing) { var pwd = String(formData.get('password') || ''); var confirmPwd = String(formData.get('confirmPassword') || ''); if (pwd !== confirmPwd) { appUtils.showMessage(messageEl, '两次输入的密码不一致', false); return; } if (pwd.length < 6) { appUtils.showMessage(messageEl, '密码长度不能少于6位', false); return; } payload.password = pwd; } appUtils.ajax({ method: isEditing ? 'PUT' : 'POST', url: isEditing ? '/api/admin/users/' + state.editingUserId : '/api/admin/users', data: payload, success: function (response) { if (!response.success) { appUtils.showMessage(messageEl, response.message || '保存失败', false); return; } appUtils.showMessage(messageEl, isEditing ? '用户信息已更新' : '用户新增成功', true); state.editingUserId = null; switchView('user-list'); loadAllAdminUsers(); }, error: function (xhr, response) { appUtils.showMessage(messageEl, (response && response.message) || '保存失败', false); } }); }); } if (clearBtn) { clearBtn.addEventListener('click', function () { state.editingUserId = null; switchView('user-list'); }); } var isEditingLocal = !!state.editingUserId; if (!isEditingLocal && uploadBtn && fileInput) { uploadBtn.addEventListener('click', function () { fileInput.click(); }); fileInput.addEventListener('change', function () { var file = fileInput.files[0]; if (!file) return; if (fileNameEl) { fileNameEl.textContent = '已选择:' + file.name; } var formDataUpload = new FormData(); formDataUpload.append('file', file); appUtils.ajax({ method: 'POST', url: '/api/admin/users/import', data: formDataUpload, success: function (response) { if (!response.success) { appUtils.showMessage(uploadMsgEl, response.message || '上传失败', false); return; } appUtils.showMessage(uploadMsgEl, response.message || '用户信息上传成功', true); state.userKeyword = ''; state.userPage = 1; loadAllAdminUsers(); setTimeout(function () { switchView('user-list'); }, 800); }, error: function (xhr, response) { appUtils.showMessage(uploadMsgEl, (response && response.message) || '上传失败', false); } }); fileInput.value = ''; if (fileNameEl) fileNameEl.textContent = ''; }); } } function bindPaginationActions() { Array.prototype.slice.call(document.querySelectorAll('.page-btn:not([disabled])')).forEach(function (button) { button.addEventListener('click', function () { var page = Number(button.getAttribute('data-page')); if (page >= 1) { state.userPage = page; renderCurrentView(); } }); }); } function bindAdminUserActions() { Array.prototype.slice.call(document.querySelectorAll('.edit-user-btn')).forEach(function (button) { button.addEventListener('click', function () { state.editingUserId = Number(button.getAttribute('data-id')); switchView('user-add'); }); }); bindUserAction('.reset-password-btn', 'POST', '/api/admin/users/{id}/reset-password', '重置密码成功'); bindUserAction('.disable-user-btn', 'POST', '/api/admin/users/{id}/disable', '账号已禁用'); bindUserAction('.enable-user-btn', 'POST', '/api/admin/users/{id}/enable', '账号已解除禁用'); bindUserAction('.delete-user-btn', 'DELETE', '/api/admin/users/{id}', '用户已删除'); } function bindUserAction(selector, method, urlTemplate, successText) { Array.prototype.slice.call(document.querySelectorAll(selector)).forEach(function (button) { button.addEventListener('click', function () { if (!confirm('确定要执行此操作吗?')) return; var userId = button.getAttribute('data-id'); appUtils.ajax({ method: method, url: urlTemplate.replace('{id}', userId), success: function (response) { if (!response.success) { window.alert(response.message || '操作失败'); return; } if (successText) { window.alert(successText); } loadAllAdminUsers(); }, error: function (xhr, response) { window.alert((response && response.message) || '操作失败'); } }); }); }); } // ===================== 参赛运动员管理 ===================== function renderAthleteManage() { if (!state.adminRegistrations.length) { mainView.innerHTML = '

暂无报名记录

当前还没有参赛人员数据。

'; return; } state.athleteTotal = state.adminRegistrations.length; var totalPages = Math.ceil(state.athleteTotal / state.athletePageSize) || 1; if (state.athletePage > totalPages) state.athletePage = totalPages; var start = (state.athletePage - 1) * state.athletePageSize; var pageItems = state.adminRegistrations.slice(start, start + state.athletePageSize); var coveredEvents = {}; state.adminRegistrations.forEach(function (item) { coveredEvents[item.eventName] = true; }); mainView.innerHTML = '' + '
' + '

数据概览

查看当前参赛人员和报名记录。

' + '
' + summaryCard('报名记录总数', state.adminRegistrations.length) + summaryCard('已覆盖项目', Object.keys(coveredEvents).length) + summaryCard('系统用户数', state.allAdminUsers.length) + '
' + '
' + '
' + '

参赛名单

显示所有已报名人员和项目详情。

' + '
' + ' ' + ' ' + ' ' + '
' + ' ' + ' ' + ' ' + pageItems.map(function (item) { return '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + ''; }).join('') + ' ' + '
姓名账号电话学院类别项目名称项目分类时间地点状态报名时间
' + escapeHtml(item.studentName) + '' + escapeHtml(item.username) + '' + escapeHtml(item.phone) + '' + escapeHtml(item.college) + '' + escapeHtml(item.category) + '' + escapeHtml(item.eventName) + '' + escapeHtml(item.eventCategory) + '' + escapeHtml((item.eventTime || '') + ' / ' + (item.location || '')) + '' + escapeHtml(item.status) + '' + escapeHtml(item.createdAt) + '
' + renderAthletePagination(totalPages) + '
'; bindAthleteActions(); } function renderAthletePagination(totalPages) { var html = ''; html += '

共 ' + state.athleteTotal + ' 条记录,第 ' + state.athletePage + ' / ' + totalPages + ' 页

'; return html; } function bindAthleteActions() { var exportBtn = document.getElementById('exportAthleteBtn'); if (exportBtn) { exportBtn.addEventListener('click', function () { var exportData = state.adminRegistrations; if (!exportData.length) { window.alert('没有可导出的参赛名单数据'); return; } var headers = ['姓名', '账号', '电话', '学院', '类别', '项目名称', '项目分类', '比赛时间', '比赛地点', '状态', '报名时间']; var csvContent = '\uFEFF' + headers.join(',') + '\n'; exportData.forEach(function (item) { var row = [ item.studentName || '', item.username || '', item.phone || '', item.college || '', item.category || '', item.eventName || '', item.eventCategory || '', item.eventTime || '', item.location || '', item.status || '', item.createdAt || '' ]; csvContent += row.map(function (v) { return '"' + String(v).replace(/"/g, '""') + '"'; }).join(',') + '\n'; }); var blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); var url = window.URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.download = '参赛名单.csv'; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); }); } bindAthletePaginationActions(); } function bindAthletePaginationActions() { Array.prototype.slice.call(document.querySelectorAll('.page-btn[data-type="athlete"]:not([disabled])')).forEach(function (button) { button.addEventListener('click', function () { var page = Number(button.getAttribute('data-page')); if (page >= 1) { state.athletePage = page; renderCurrentView(); } }); }); } // ===================== 项目列表 ===================== function renderEventList() { state.adminEventTotal = state.adminEvents.length; var totalPages = Math.ceil(state.adminEventTotal / state.adminEventPageSize) || 1; if (state.adminEventPage > totalPages) state.adminEventPage = totalPages; var start = (state.adminEventPage - 1) * state.adminEventPageSize; var pageItems = state.adminEvents.slice(start, start + state.adminEventPageSize); mainView.innerHTML = '' + '
' + '

项目列表

查看、编辑和删除当前项目。

' + (state.adminEvents.length ? '' + '' + ' ' + ' ' + pageItems.map(function (item) { return '' + '' + '' + '' + '' + '' + '' + '' + '' + ''; }).join('') + ' ' + '
项目名称项目分类比赛时间比赛地点人数上限已报名项目说明操作
' + escapeHtml(item.eventName) + '' + escapeHtml(item.eventCategory) + '' + escapeHtml(item.eventTime) + '' + escapeHtml(item.location) + '' + escapeHtml(item.quota) + '' + escapeHtml(item.registeredCount || 0) + '' + escapeHtml(item.description) + '' + '' + '' + '
' + renderAdminEventPagination(totalPages) : '

暂无项目

请先到新增项目页面添加比赛项目。

') + '
'; bindEventListActions(); } function renderAdminEventPagination(totalPages) { var html = ''; html += '

共 ' + state.adminEventTotal + ' 条记录,第 ' + state.adminEventPage + ' / ' + totalPages + ' 页

'; return html; } function bindEventListActions() { Array.prototype.slice.call(document.querySelectorAll('.edit-event-btn')).forEach(function (button) { button.addEventListener('click', function () { state.editingEventId = Number(button.getAttribute('data-id')); switchView('event-add'); }); }); Array.prototype.slice.call(document.querySelectorAll('.delete-event-btn')).forEach(function (button) { button.addEventListener('click', function () { if (!confirm('确定要删除该项目吗?')) return; var eventId = button.getAttribute('data-id'); appUtils.ajax({ method: 'DELETE', url: '/api/admin/events/' + eventId, success: function (response) { if (!response.success) { window.alert(response.message || '删除失败'); return; } if (state.editingEventId === Number(eventId)) { state.editingEventId = null; } loadAdminData(); }, error: function (xhr, response) { window.alert((response && response.message) || '删除失败'); } }); }); }); bindAdminEventPaginationActions(); } function bindAdminEventPaginationActions() { Array.prototype.slice.call(document.querySelectorAll('.page-btn[data-type="adminEvent"]:not([disabled])')).forEach(function (button) { button.addEventListener('click', function () { var page = Number(button.getAttribute('data-page')); if (page >= 1) { state.adminEventPage = page; renderCurrentView(); } }); }); } // ===================== 新增项目 ===================== function renderEventAdd() { var editingItem = state.adminEvents.find(function (item) { return item.id === state.editingEventId; }) || { eventName: '', eventCategory: '', location: '', quota: '', eventTime: '', description: '' }; mainView.innerHTML = '' + '
' + '

' + (state.editingEventId ? '编辑项目' : '新增项目') + '

在这里维护比赛项目基础信息。

' + '
' + fieldInput('项目名称', 'eventName', editingItem.eventName, '请输入项目名称') + fieldInput('项目分类', 'eventCategory', editingItem.eventCategory, '请输入项目分类') + fieldInput('比赛地点', 'location', editingItem.location, '请输入比赛地点') + ' ' + fieldInput('比赛时间', 'eventTime', editingItem.eventTime, '例如 2026-05-20 08:30', true) + fieldInput('项目说明', 'description', editingItem.description, '请输入项目说明', true) + '
' + ' ' + ' ' + '
' + '

' + '
' + '
'; bindEventAddActions(); } function bindEventAddActions() { var form = document.getElementById('eventForm'); var resetButton = document.getElementById('resetEventForm'); var messageEl = document.getElementById('eventFormMessage'); if (form) { form.addEventListener('submit', function (event) { event.preventDefault(); var formData = new FormData(form); var payload = { eventName: String(formData.get('eventName') || '').trim(), eventCategory: String(formData.get('eventCategory') || '').trim(), location: String(formData.get('location') || '').trim(), quota: Number(formData.get('quota') || 0), eventTime: String(formData.get('eventTime') || '').trim(), description: String(formData.get('description') || '').trim() }; appUtils.ajax({ method: state.editingEventId ? 'PUT' : 'POST', url: state.editingEventId ? '/api/admin/events/' + state.editingEventId : '/api/admin/events', data: payload, success: function (response) { if (!response.success) { appUtils.showMessage(messageEl, response.message || '保存失败', false); return; } state.editingEventId = null; appUtils.showMessage(messageEl, '项目保存成功', true); loadAdminData(); setTimeout(function () { renderCurrentView(); }, 800); }, error: function (xhr, response) { appUtils.showMessage(messageEl, (response && response.message) || '保存失败', false); } }); }); } if (resetButton) { resetButton.addEventListener('click', function () { state.editingEventId = null; renderCurrentView(); }); } } // ===================== 占位页面 ===================== function renderPlaceholder(title, desc) { mainView.innerHTML = '' + '
' + '

' + escapeHtml(title) + '

' + escapeHtml(desc) + '

' + '

功能持续完善中

这个模块的详细功能可以继续在现有框架上补充。

' + '
'; } // ===================== 视图路由 ===================== function renderCurrentView() { var meta = getMeta(state.currentView); sectionTitle.textContent = meta.label; sectionDesc.textContent = meta.desc; renderTabs(); if (state.currentView === 'profile-view') { renderProfileView(); return; } if (state.currentView === 'profile-edit') { renderProfileEdit(); return; } if (state.currentView === 'events') { renderEventTable(state.currentTab === 'mine' ? state.myEvents : state.events, state.currentTab === 'mine'); return; } if (state.currentView === 'admin-home') { renderAdminHome(); return; } if (state.currentView === 'user-list') { renderUserList(); return; } if (state.currentView === 'user-add') { renderUserAdd(); return; } if (state.currentView === 'event-list') { renderEventList(); return; } if (state.currentView === 'event-add') { renderEventAdd(); return; } if (state.currentView === 'athlete-manage') { renderAthleteManage(); return; } if (state.currentView === 'team-info') { renderPlaceholder('团体信息管理', '可继续补充学院、班级、代表队等团体信息管理功能。'); return; } if (state.currentView === 'score-manage') { renderPlaceholder('参赛成绩管理', '可继续补充成绩录入、成绩维护、成绩查询等功能。'); return; } if (state.currentView === 'record-manage') { renderPlaceholder('项目记录管理', '可继续补充项目记录、秩序册和赛事档案管理功能。'); return; } if (state.currentView === 'system-manage') { renderPlaceholder('系统管理', '可继续补充系统配置、权限设置和运行维护功能。'); return; } renderPlaceholder('系统首页', '欢迎进入运动会报名系统。'); } function switchView(view) { state.currentView = view; if (view === 'events' && state.currentTab !== 'mine') { state.currentTab = 'all'; } if (view === 'user-list') { state.userPage = 1; } renderSidebar(); renderCurrentView(); } // ===================== 数据加载 ===================== function loadEventData() { appUtils.ajax({ method: 'GET', url: '/api/events', success: function (response) { if (response.success) { state.events = response.data || []; if (state.currentView === 'events') { renderCurrentView(); } } } }); appUtils.ajax({ method: 'GET', url: '/api/events/my', success: function (response) { if (response.success) { state.myEvents = response.data || []; if (state.currentView === 'events') { renderCurrentView(); } } } }); } function loadAdminData() { if (!state.user || state.user.role !== 'ADMIN') { return; } appUtils.ajax({ method: 'GET', url: '/api/admin/users', success: function (response) { if (response.success) { state.allAdminUsers = response.data || []; state.adminUsers = response.data || []; renderCurrentView(); } } }); appUtils.ajax({ method: 'GET', url: '/api/admin/registrations', success: function (response) { if (response.success) { state.adminRegistrations = response.data || []; renderCurrentView(); } } }); appUtils.ajax({ method: 'GET', url: '/api/admin/events', success: function (response) { if (response.success) { state.adminEvents = response.data || []; renderCurrentView(); } } }); appUtils.ajax({ method: 'GET', url: '/api/admin/dashboard/stats', success: function (response) { if (response.success && response.data) { state.dashboardStats = response.data; renderCurrentView(); } } }); } function loadAllAdminUsers() { if (!state.user || state.user.role !== 'ADMIN') { return; } appUtils.ajax({ method: 'GET', url: '/api/admin/users', success: function (response) { if (response.success) { state.allAdminUsers = response.data || []; state.adminUsers = response.data || []; renderCurrentView(); } } }); } // ===================== 辅助函数 ===================== function loadCurrentUser() { appUtils.ajax({ method: 'GET', url: '/api/users/me', success: function (response) { if (!response.success || !response.data) { window.location.href = './login.html'; return; } state.user = response.data; state.currentView = state.user.role === 'ADMIN' ? 'admin-home' : 'profile-view'; updateCurrentUserName(); renderSidebar(); renderCurrentView(); loadEventData(); loadAdminData(); }, error: function () { window.location.href = './login.html'; } }); } function updateCurrentUserName() { currentUserName.textContent = state.user.name + (state.user.role === 'ADMIN' ? ' 管理员' : ' 用户'); } function fieldInput(label, name, value, placeholder, fullRow) { return ' '; } function fieldSelect(label, name, value, options, placeholder, fullRow) { return ' '; } function infoCard(label, value) { return '
' + label + '

' + escapeHtml(value) + '

'; } function summaryCard(label, value) { return '
' + label + '

' + escapeHtml(value) + '

'; } logoutBtn.addEventListener('click', function () { appUtils.ajax({ method: 'POST', url: '/api/auth/logout', success: function () { window.location.href = './login.html'; }, error: function () { window.location.href = './login.html'; } }); }); loadCurrentUser(); });