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 += '
';
html += '
';
html += '
';
for (var c = 0; c < item.children.length; c++) {
var child = item.children[c];
html += '';
}
html += '
';
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 = ''
+ ''
+ '
修改个人信息
可在此更新姓名、电话、学院、班级、学号等信息。
'
+ '
'
+ '
';
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 ''
+ '| ' + escapeHtml(item.eventName) + ' | '
+ '' + escapeHtml(item.eventCategory) + ' | '
+ '' + escapeHtml(item.eventTime) + ' | '
+ '' + escapeHtml(item.location) + ' | '
+ '' + escapeHtml((item.registeredCount || 0) + '/' + (item.quota || 0)) + ' | '
+ '' + escapeHtml(item.description) + ' | '
+ '' + buttonHtml + ' | '
+ '
';
}).join('')
+ ' '
+ '
'
+ 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 ''
+ '| ' + 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 + ' | '
+ '
';
}).join('')
+ ' '
+ '
'
+ 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 ? '修改现有用户的基本信息。' : '管理员可在此新增系统用户。') + '
'
+ '
'
+ '
'
+ (!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 ''
+ '| ' + 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) + ' | '
+ '
';
}).join('')
+ ' '
+ '
'
+ 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 ''
+ '| ' + escapeHtml(item.eventName) + ' | '
+ '' + escapeHtml(item.eventCategory) + ' | '
+ '' + escapeHtml(item.eventTime) + ' | '
+ '' + escapeHtml(item.location) + ' | '
+ '' + escapeHtml(item.quota) + ' | '
+ '' + escapeHtml(item.registeredCount || 0) + ' | '
+ '' + escapeHtml(item.description) + ' | '
+ ''
+ ''
+ ''
+ ' | '
+ '
';
}).join('')
+ ' '
+ '
'
+ 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 ? '编辑项目' : '新增项目') + '
在这里维护比赛项目基础信息。
'
+ '
'
+ '
';
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();
});