From e1ccdd140bd23953ba29f0a29b803f21568f91c9 Mon Sep 17 00:00:00 2001 From: lyq0314 <2169694180@qq.com> Date: Wed, 13 May 2026 08:52:24 +0800 Subject: [PATCH] first commit --- .gitignore | 67 +++++ README.md | 107 ++++++++ app.html | 51 ++++ assets/css/style.css | 522 +++++++++++++++++++++++++++++++++++++ assets/js/app.js | 590 ++++++++++++++++++++++++++++++++++++++++++ assets/js/common.js | 71 +++++ assets/js/login.js | 55 ++++ assets/js/register.js | 45 ++++ login.html | 53 ++++ register.html | 100 +++++++ 10 files changed, 1661 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 app.html create mode 100644 assets/css/style.css create mode 100644 assets/js/app.js create mode 100644 assets/js/common.js create mode 100644 assets/js/login.js create mode 100644 assets/js/register.js create mode 100644 login.html create mode 100644 register.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e8239f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,67 @@ + +# Compiled class files +*.class + +# Log files +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package files +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs +hs_err_pid* +replay_pid* + +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +# IDE +.idea/ +*.iml +*.ipr +*.iws +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Frontend +node_modules/ +dist/ +build/ +npm-debug.log +yarn-error.log + +# Database +*.db +*.sqlite + +# SSL certificates +*.pem +*.key +*.crt diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c86140 --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ + +# 运动会报名系统 + +## 项目简介 + +这是一个基于 Spring Boot 和前端技术栈开发的运动会报名系统,用于管理学校运动会的项目报名工作。 + +## 技术栈 + +### 后端 +- Java 8+ +- Spring Boot 2.x +- MyBatis +- MySQL 数据库 + +### 前端 +- HTML5 / CSS3 / JavaScript +- Bootstrap 样式框架 + +## 项目结构 + +``` +运动会报名/ +├── backend/ # 后端代码 +│ ├── src/main/java/ # Java 源代码 +│ ├── src/main/resources/ # 配置文件 +│ └── pom.xml # Maven 配置 +├── frontend/ # 前端代码 +│ ├── assets/ # 静态资源 +│ │ ├── css/ # 样式文件 +│ │ └── js/ # JavaScript 文件 +│ ├── index.html # 首页 +│ ├── login.html # 登录页 +│ ├── register.html # 注册页 +│ └── app.html # 主应用页面 +└── README.md # 项目说明 +``` + +## 功能模块 + +### 用户功能 +- 用户注册与登录 +- 个人信息管理 +- 运动会项目浏览 +- 项目报名与取消 + +### 管理员功能 +- 用户信息管理 +- 报名总览查看 +- 报名记录统计 + +## 快速开始 + +### 环境要求 +- JDK 8 或更高版本 +- Maven 3.6+ +- MySQL 5.7+ + +### 数据库配置 + +创建数据库并导入初始数据: + +```sql +CREATE DATABASE sports_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE sports_db; +``` + +导入 `backend/src/main/resources/schema.sql` 和 `backend/src/main/resources/data.sql` + +### 启动后端服务 + +```bash +cd backend +mvn spring-boot:run +``` + +### 启动前端服务 + +使用任意 HTTP 服务器启动前端,例如: + +```bash +cd frontend +python -m http.server 8080 +``` + +### 访问地址 + +- 前端页面: http://localhost:8080 +- 后端 API: http://localhost:8081 + +## 默认账号 + +| 账号 | 密码 | 角色 | +|------|------|------| +| admin | admin | 管理员 | +| student | student | 普通用户 | + +## 开发说明 + +### 代码规范 +- Java 代码遵循 Spring 编码规范 +- JavaScript 代码使用 ES6+ 语法 +- 数据库表名使用下划线命名 + +### 注意事项 +- 开发环境下请确保 MySQL 服务已启动 +- 修改配置文件后需要重启服务才能生效 diff --git a/app.html b/app.html new file mode 100644 index 0000000..a171c98 --- /dev/null +++ b/app.html @@ -0,0 +1,51 @@ + + + + + + + 运动会报名系统 + + + + +
+
+
+

Sports Meet Workspace

+

运动会报名系统

+
+
+ 加载中... + +
+
+ +
+ + +
+
+
+

个人信息

+

查看并维护当前登录人员的基础资料。

+
+ +
+
+ + + +
+
+
+ + + + + \ No newline at end of file diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..8f9f6fb --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,522 @@ +:root { + --surface: rgba(255, 255, 255, 0.92); + --text: #1f2937; + --muted: #6b7280; + --line: rgba(148, 163, 184, 0.24); + --primary: #0f766e; + --primary-light: #14b8a6; + --success: #059669; + --danger: #dc2626; + --shadow: 0 24px 60px rgba(15, 23, 42, 0.14); + --radius-xl: 28px; + --radius-lg: 20px; + --radius-md: 14px; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + font-family: "Microsoft YaHei", "PingFang SC", sans-serif; + color: var(--text); + background: + radial-gradient(circle at top left, rgba(15, 118, 110, 0.18), transparent 28%), + radial-gradient(circle at bottom right, rgba(245, 158, 11, 0.16), transparent 20%), + linear-gradient(180deg, #f8fbff 0%, #eef4f8 100%); +} + +button, +input, +select { + font: inherit; +} + +.hidden { + display: none !important; +} + +.auth-page { + display: flex; + align-items: center; + justify-content: center; + padding: 32px 20px; +} + +.auth-shell { + width: min(1180px, 100%); + display: grid; + gap: 24px; +} + +.login-shell { + grid-template-columns: 1.2fr 0.9fr; +} + +.register-shell { + grid-template-columns: 1.1fr 0.8fr; +} + +.auth-hero, +.auth-panel, +.register-side-card, +.topbar, +.sidebar, +.content-card { + background: var(--surface); + border: 1px solid rgba(255, 255, 255, 0.55); + border-radius: var(--radius-xl); + box-shadow: var(--shadow); + backdrop-filter: blur(18px); +} + +.auth-hero, +.auth-panel, +.register-side-card, +.content-card { + padding: 32px; +} + +.auth-badge, +.topbar-tag, +.pill { + display: inline-flex; + align-items: center; + padding: 8px 14px; + border-radius: 999px; + background: rgba(15, 118, 110, 0.12); + color: #115e59; + font-size: 13px; + font-weight: 700; +} + +.auth-hero h1, +.register-side-card h1 { + margin: 18px 0 12px; + line-height: 1.08; + font-size: clamp(32px, 4vw, 52px); +} + +.auth-subtitle, +.panel-head p, +.register-side-card p, +.notice-card li, +.field span, +.content-header p, +.info-meta, +.empty-state p { + color: var(--muted); +} + +.notice-card { + margin-top: 24px; + padding: 24px; + border-radius: var(--radius-lg); + background: linear-gradient(135deg, rgba(15, 118, 110, 0.08), rgba(255, 255, 255, 0.85)); + border: 1px solid rgba(15, 118, 110, 0.14); +} + +.notice-card h2, +.panel-head h2, +.content-header h2 { + margin: 0 0 10px; +} + +.notice-card ul { + margin: 0; + padding-left: 18px; + display: grid; + gap: 10px; +} + +.panel-head { + margin-bottom: 24px; +} + +.form-grid { + display: grid; + gap: 18px; +} + +.single-column { + grid-template-columns: 1fr; +} + +.two-columns { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.field { + display: grid; + gap: 10px; +} + +.field span { + font-size: 14px; + font-weight: 700; +} + +.field input, +.field select { + width: 100%; + border: 1px solid var(--line); + background: rgba(255, 255, 255, 0.95); + border-radius: 16px; + padding: 10px 16px; + outline: none; +} + +/* 下拉列表通用样式 */ +select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: url('data:image/svg+xml;charset=US-ASCII,'); + background-repeat: no-repeat; + background-position: right 16px center; + background-size: 16px; +} + +/* 学院下拉列表选项样式 */ +select[name="college"] option { + padding: 6px 16px; + line-height: 1.3; +} + +/* 学院下拉列表占位符样式 */ +select[name="college"] option[value=""] { + color: var(--muted); + display: none; +} + +/* 未选择时显示占位符颜色 */ +select[name="college"]:invalid { + color: var(--muted); +} + +/* 选择后显示正常颜色 */ +select[name="college"]:valid { + color: var(--text); +} + +.form-actions { + display: flex; + gap: 14px; + align-items: center; +} + +.form-actions.stacked { + flex-direction: column; +} + +.full-row { + grid-column: 1 / -1; +} + +.primary-btn, +.ghost-btn, +.nav-item, +.tab-item, +.action-btn { + border: none; + cursor: pointer; + transition: transform 0.2s ease, background 0.2s ease; +} + +.primary-btn, +.ghost-btn { + min-height: 48px; + padding: 0 22px; + border-radius: 16px; + font-weight: 700; +} + +.primary-btn { + background: linear-gradient(135deg, var(--primary), var(--primary-light)); + color: #fff; +} + +.ghost-btn { + background: rgba(255, 255, 255, 0.8); + color: var(--text); + border: 1px solid var(--line); +} + +.small { + min-height: 40px; + padding: 0 16px; +} + +.form-message { + min-height: 22px; + margin: 0; + font-size: 14px; +} + +.form-message.success { + color: var(--success); +} + +.form-message.error { + color: var(--danger); +} + +.app-body { + padding: 24px; +} + +.app-layout { + display: grid; + gap: 18px; +} + +.topbar { + padding: 22px 28px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.topbar h1 { + margin: 10px 0 0; + font-size: 30px; +} + +.topbar-user { + display: flex; + align-items: center; + gap: 12px; + font-weight: 700; +} + +.main-layout { + display: grid; + grid-template-columns: 220px 1fr; + gap: 18px; +} + +.sidebar { + padding: 18px; + display: grid; + gap: 10px; + align-content: start; +} + +.nav-item { + width: 100%; + text-align: left; + padding: 16px 18px; + border-radius: 18px; + background: transparent; + color: var(--text); + font-weight: 700; +} + +.nav-item.active, +.tab-item.active { + background: linear-gradient(135deg, rgba(15, 118, 110, 0.14), rgba(20, 184, 166, 0.24)); + color: #115e59; +} + +.content-area { + display: grid; + gap: 18px; +} + +.content-header, +.section-head { + display: flex; + justify-content: space-between; + align-items: center; +} + +.content-tabs { + display: flex; + gap: 10px; +} + +.tab-item { + min-width: 110px; + padding: 12px 18px; + border-radius: 14px; + background: rgba(255, 255, 255, 0.72); + font-weight: 700; +} + +.profile-grid, +.summary-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 18px; +} + +.summary-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-bottom: 22px; +} + +.info-card, +.summary-card { + padding: 18px; + border-radius: 20px; + background: rgba(248, 250, 252, 0.96); + border: 1px solid rgba(226, 232, 240, 0.9); +} + +.summary-card strong, +.info-card strong { + display: block; + margin-bottom: 8px; +} + +.summary-card p, +.info-card p { + margin: 0; + font-size: 16px; +} + +.section-block+.section-block { + margin-top: 28px; +} + +.section-head { + margin-bottom: 18px; +} + +.event-table { + width: 100%; + border-collapse: collapse; + border-radius: 18px; + overflow: hidden; + background: rgba(255, 255, 255, 0.86); +} + +.event-table th, +.event-table td { + padding: 15px 14px; + text-align: left; + border-bottom: 1px solid rgba(226, 232, 240, 0.9); + font-size: 14px; +} + +.event-table th:last-child, +.event-table td:last-child { + text-align: center; +} + +.event-table th { + background: rgba(15, 118, 110, 0.08); + color: #115e59; +} + +.event-table tr:last-child td { + border-bottom: none; +} + +.action-btn { + padding: 10px 16px; + border-radius: 12px; + background: linear-gradient(135deg, var(--primary), var(--primary-light)); + color: #fff; + font-weight: 700; +} + +.action-btn[disabled] { + background: rgba(148, 163, 184, 0.6); + cursor: not-allowed; +} + +.empty-state { + padding: 32px 20px; + text-align: center; + border-radius: 22px; + background: rgba(248, 250, 252, 0.86); + border: 1px dashed rgba(148, 163, 184, 0.45); +} + +/* 分页样式 */ +.pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 8px; + margin-top: 20px; + flex-wrap: wrap; +} + +.page-btn { + min-width: 36px; + height: 36px; + padding: 0 12px; + border: 1px solid var(--line); + border-radius: 8px; + background: rgba(255, 255, 255, 0.95); + color: var(--text); + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; +} + +.page-btn:hover:not(:disabled) { + background: rgba(15, 118, 110, 0.08); + border-color: var(--primary); +} + +.page-btn.active { + background: linear-gradient(135deg, var(--primary), var(--primary-light)); + color: #fff; + border-color: transparent; +} + +.page-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.page-info { + text-align: center; + margin-top: 12px; + font-size: 14px; + color: var(--muted); +} + +@media (max-width: 960px) { + + .login-shell, + .register-shell, + .main-layout, + .profile-grid, + .summary-grid, + .two-columns { + grid-template-columns: 1fr; + } + + .topbar, + .content-header, + .section-head { + flex-direction: column; + align-items: flex-start; + gap: 14px; + } +} + +@media (max-width: 640px) { + + body, + .app-body { + padding: 14px; + } + + .form-actions { + flex-direction: column; + align-items: stretch; + } + + .event-table { + display: block; + overflow-x: auto; + } +} \ No newline at end of file diff --git a/assets/js/app.js b/assets/js/app.js new file mode 100644 index 0000000..059cb0c --- /dev/null +++ b/assets/js/app.js @@ -0,0 +1,590 @@ +document.addEventListener('DOMContentLoaded', function () { + var currentUserName = document.getElementById('currentUserName'); + var logoutBtn = document.getElementById('logoutBtn'); + var navItems = document.querySelectorAll('.nav-item'); + var sectionTitle = document.getElementById('sectionTitle'); + var sectionDesc = document.getElementById('sectionDesc'); + var subTabs = document.getElementById('subTabs'); + var profileView = document.getElementById('profileView'); + var eventsView = document.getElementById('eventsView'); + var adminView = document.getElementById('adminView'); + var registrationsView = document.getElementById('registrationsView'); + var adminNavBtn = document.getElementById('adminNavBtn'); + var registrationsNavBtn = document.getElementById('registrationsNavBtn'); + + var state = { + currentView: 'profile', + currentTab: 'all', + user: null, + events: [], + myEvents: [], + adminUsers: [], + adminRegistrations: [], + eventPage: 1, + myEventPage: 1, + registrationsPage: 1, + pageSize: 7 + }; + + function escapeHtml(value) { + return String(value == null ? '' : value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + function buildOptions(list, current) { + return list.map(function (item) { + return ''; + }).join(''); + } + + function buildCollegeOptions(current) { + var colleges = [ + '文学与文化传播学院', + '马克思主义学院', + '教育学院', + '外国语学院', + '历史文化学院', + '商学院', + '化学工程与技术学院', + '电子信息与电气工程学院', + '数学与统计学院', + '生物工程与技术学院', + '机电工程学院', + '土木工程学院', + '资源与环境工程学院', + '体育学院', + '美术与设计学院', + '音乐舞蹈学院', + '卫生健康学院', + '继续教育学院(培训中心)' + ]; + return '' + colleges.map(function (item) { + return ''; + }).join(''); + } + + function getPaginatedList(list, page) { + var start = (page - 1) * state.pageSize; + var end = start + state.pageSize; + return list.slice(start, end); + } + + function getTotalPages(list) { + return Math.ceil(list.length / state.pageSize); + } + + function renderPagination(totalPages, currentPage, type) { + if (totalPages <= 1) { + return ''; + } + var html = ''; + return html; + } + + function renderTabs() { + var html = ''; + if (state.currentView === 'events') { + html = '' + + '' + + ''; + } else if (state.currentView === 'admin') { + html = ''; + } + subTabs.innerHTML = html; + subTabs.classList.toggle('hidden', !html); + Array.prototype.slice.call(subTabs.querySelectorAll('.tab-item')).forEach(function (item) { + item.addEventListener('click', function () { + state.currentTab = item.getAttribute('data-tab'); + renderTabs(); + renderCurrentView(); + }); + }); + } + + function renderProfile() { + if (!state.user) { + return; + } + profileView.innerHTML = '' + + '
' + + '

基础资料

支持在线更新姓名、电话、性别、学院和类别信息。

' + + '
' + + '
身份证号

' + escapeHtml(state.user.idCard) + '

' + + '
登录账号

' + escapeHtml(state.user.username) + '

' + + '
姓名

' + escapeHtml(state.user.name) + '

' + + '
联系电话

' + escapeHtml(state.user.phone) + '

' + + '
性别

' + escapeHtml(state.user.gender) + '

' + + '
学院

' + escapeHtml(state.user.college) + '

' + + '
类别

' + escapeHtml(state.user.category) + '

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

修改信息

请保持信息真实准确,方便赛事组织和通知。

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

' + + '
' + + '
'; + + document.getElementById('profileForm').addEventListener('submit', function (event) { + event.preventDefault(); + var formData = new FormData(event.target); + 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') || ''), + college: String(formData.get('college') || '').trim(), + category: String(formData.get('category') || '') + }, + success: function (response) { + if (!response.success) { + appUtils.showMessage(document.getElementById('profileMessage'), response.message || '保存失败', false); + return; + } + state.user = response.data; + currentUserName.textContent = state.user.name + (state.user.role === 'ADMIN' ? ' 管理员' : ' 老师'); + renderProfile(); + appUtils.showMessage(document.getElementById('profileMessage'), '个人信息已更新', true); + }, + error: function (xhr, response) { + appUtils.showMessage(document.getElementById('profileMessage'), (response && response.message) || '保存失败', false); + } + }); + }); + } + + function renderEventTable(list, isMine) { + if (!list.length) { + return '

暂无数据

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

'; + } + var currentPage = isMine ? state.myEventPage : state.eventPage; + var totalPages = getTotalPages(list); + var paginatedList = getPaginatedList(list, currentPage); + + var html = '' + + '' + + ' ' + + ' ' + + paginatedList.map(function (item) { + var actionHtml = isMine + ? '' + : ''; + return '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + }).join('') + + ' ' + + '
项目名称项目类别比赛时间比赛地点报名情况项目说明操作
' + escapeHtml(item.eventName) + '' + escapeHtml(item.eventCategory) + '' + escapeHtml(item.eventTime) + '' + escapeHtml(item.location) + '' + escapeHtml(item.registeredCount + '/' + item.quota) + '' + escapeHtml(item.description) + '' + actionHtml + '
'; + + html += renderPagination(totalPages, currentPage, isMine); + html += '
第 ' + currentPage + ' 页 / 共 ' + totalPages + ' 页,共 ' + list.length + ' 条记录
'; + return html; + } + + function renderUserTable() { + if (!state.adminUsers.length) { + return '

暂无用户

当前系统还没有用户数据。

'; + } + return '' + + '' + + '' + + '' + + state.adminUsers.map(function (item) { + return '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + }).join('') + + '
姓名账号身份证号电话性别学院类别角色操作
' + escapeHtml(item.name) + '' + escapeHtml(item.username) + '' + escapeHtml(item.idCard) + '' + escapeHtml(item.phone) + '' + escapeHtml(item.gender) + '' + escapeHtml(item.college) + '' + escapeHtml(item.category) + '' + escapeHtml(item.role === 'ADMIN' ? '管理员' : '普通用户') + '' + + ' ' + + '' + + '
'; + } + + function bindUserActions() { + // 绑定重置密码按钮事件 + Array.prototype.slice.call(document.querySelectorAll('.reset-btn')).forEach(function (button) { + button.addEventListener('click', function () { + var userId = button.getAttribute('data-id'); + if (confirm('确定要重置该用户的密码吗?重置后密码将变为默认值。')) { + appUtils.ajax({ + method: 'POST', + url: '/api/admin/users/' + userId + '/reset-password', + success: function (response) { + if (response.success) { + alert('密码重置成功!'); + } else { + alert('密码重置失败:' + (response.message || '未知错误')); + } + }, + error: function (xhr, response) { + alert('密码重置失败:' + (response && response.message) || '网络异常'); + } + }); + } + }); + }); + + // 绑定删除账号按钮事件 + Array.prototype.slice.call(document.querySelectorAll('.delete-btn')).forEach(function (button) { + button.addEventListener('click', function () { + var userId = button.getAttribute('data-id'); + if (confirm('确定要删除该账号吗?此操作不可恢复。')) { + appUtils.ajax({ + method: 'DELETE', + url: '/api/admin/users/' + userId, + success: function (response) { + if (response.success) { + alert('账号删除成功!'); + loadAdminData(); + } else { + alert('账号删除失败:' + (response.message || '未知错误')); + } + }, + error: function (xhr, response) { + alert('账号删除失败:' + (response && response.message) || '网络异常'); + } + }); + } + }); + }); + } + + function uniqueEventCount() { + var map = {}; + state.adminRegistrations.forEach(function (item) { + map[item.eventName] = true; + }); + return Object.keys(map).length; + } + + function renderRegistrationTable() { + var paginatedRegistrations = getPaginatedList(state.adminRegistrations, state.registrationsPage); + if (!paginatedRegistrations.length) { + return '

暂无报名记录

目前还没有用户完成项目报名。

'; + } + return '' + + '' + + '' + + '' + + paginatedRegistrations.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) + '
'; + } + + function renderAdminContent() { + adminView.innerHTML = '' + + '
' + + '
用户总数

' + state.adminUsers.length + '

' + + '
管理员数量

' + state.adminUsers.filter(function (item) { return item.role === "ADMIN"; }).length + '

' + + '
普通用户数量

' + state.adminUsers.filter(function (item) { return item.role !== "ADMIN"; }).length + '

' + + '
' + + renderUserTable(); + bindUserActions(); + } + + function bindRegisterButtons() { + // 绑定报名按钮事件 + Array.prototype.slice.call(document.querySelectorAll('.register-btn')).forEach(function (button) { + button.addEventListener('click', function () { + appUtils.ajax({ + method: 'POST', + url: '/api/events/' + button.getAttribute('data-id') + '/register', + success: function (response) { + if (!response.success) { + window.alert(response.message || '报名失败'); + return; + } + window.alert('报名成功'); + 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 () { + if (confirm('确定要取消报名吗?')) { + appUtils.ajax({ + method: 'DELETE', + url: '/api/events/' + button.getAttribute('data-id') + '/register', + success: function (response) { + if (!response.success) { + window.alert(response.message || '取消报名失败'); + return; + } + window.alert('取消报名成功'); + loadEventData(); + }, + error: function (xhr, response) { + window.alert((response && response.message) || '取消报名失败'); + } + }); + } + }); + }); + } + + function bindPaginationButtons(type) { + var totalPages, currentPage; + if (type === 'registrations') { + totalPages = getTotalPages(state.adminRegistrations); + currentPage = state.registrationsPage; + } else { + var isMine = type === 'mine'; + totalPages = getTotalPages(isMine ? state.myEvents : state.events); + currentPage = isMine ? state.myEventPage : state.eventPage; + } + + Array.prototype.slice.call(document.querySelectorAll('.pagination .page-btn')).forEach(function (button) { + button.addEventListener('click', function () { + var page = button.getAttribute('data-page'); + + if (page === 'prev') { + currentPage = Math.max(1, currentPage - 1); + } else if (page === 'next') { + currentPage = Math.min(totalPages, currentPage + 1); + } else { + currentPage = parseInt(page, 10); + } + + if (type === 'registrations') { + state.registrationsPage = currentPage; + var totalPages = getTotalPages(state.adminRegistrations); + registrationsView.innerHTML = '' + + '
' + + '
报名记录总数

' + state.adminRegistrations.length + '

' + + '
已报名状态

' + state.adminRegistrations.filter(function (item) { return item.status === "已报名"; }).length + '

' + + '
覆盖项目数

' + uniqueEventCount() + '

' + + '
' + + renderRegistrationTable() + + renderPagination(totalPages, state.registrationsPage, 'registrations'); + bindPaginationButtons('registrations'); + } else { + var isMine = type === 'mine'; + if (isMine) { + state.myEventPage = currentPage; + } else { + state.eventPage = currentPage; + } + + eventsView.innerHTML = renderEventTable(isMine ? state.myEvents : state.events, isMine); + bindRegisterButtons(); + bindPaginationButtons(type); + } + }); + }); + } + + function renderCurrentView() { + profileView.classList.toggle('hidden', state.currentView !== 'profile'); + eventsView.classList.toggle('hidden', state.currentView !== 'events'); + adminView.classList.toggle('hidden', state.currentView !== 'admin'); + registrationsView.classList.toggle('hidden', state.currentView !== 'registrations'); + + if (state.currentView === 'profile') { + sectionTitle.textContent = '个人信息'; + sectionDesc.textContent = '查看并维护当前登录人员的基础资料。'; + renderTabs(); + renderProfile(); + return; + } + + if (state.currentView === 'events') { + sectionTitle.textContent = '运动会报名'; + sectionDesc.textContent = '浏览所有项目并完成报名,也可以查看自己的报名信息。'; + renderTabs(); + eventsView.innerHTML = renderEventTable(state.currentTab === 'mine' ? state.myEvents : state.events, state.currentTab === 'mine'); + bindRegisterButtons(); + bindPaginationButtons(state.currentTab === 'mine' ? 'mine' : 'all'); + return; + } + + if (state.currentView === 'admin') { + sectionTitle.textContent = '管理员后台'; + sectionDesc.textContent = '查看系统内所有用户资料。'; + renderTabs(); + renderAdminContent(); + return; + } + + if (state.currentView === 'registrations') { + sectionTitle.textContent = '报名总览'; + sectionDesc.textContent = '查看所有用户的报名记录。'; + subTabs.classList.add('hidden'); + var totalPages = getTotalPages(state.adminRegistrations); + registrationsView.innerHTML = '' + + '
' + + '
报名记录总数

' + state.adminRegistrations.length + '

' + + '
已报名状态

' + state.adminRegistrations.filter(function (item) { return item.status === "已报名"; }).length + '

' + + '
覆盖项目数

' + uniqueEventCount() + '

' + + '
' + + renderRegistrationTable() + + renderPagination(totalPages, state.registrationsPage, 'registrations'); + bindPaginationButtons('registrations'); + return; + } + } + + function loadEventData() { + appUtils.ajax({ + method: 'GET', + url: '/api/events', + success: function (response) { + if (response.success) { + state.events = response.data || []; + renderCurrentView(); + } + } + }); + appUtils.ajax({ + method: 'GET', + url: '/api/events/my', + success: function (response) { + if (response.success) { + state.myEvents = response.data || []; + 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.adminUsers = response.data || []; + renderCurrentView(); + } + } + }); + appUtils.ajax({ + method: 'GET', + url: '/api/admin/registrations', + success: function (response) { + if (response.success) { + state.adminRegistrations = response.data || []; + renderCurrentView(); + } + } + }); + } + + function switchView(view) { + state.currentView = view; + if (view === 'events') { + state.currentTab = state.currentTab === 'mine' ? 'mine' : 'all'; + } + navItems.forEach(function (item) { + item.classList.toggle('active', item.getAttribute('data-view') === view); + }); + 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; + currentUserName.textContent = state.user.name + (state.user.role === 'ADMIN' ? ' 管理员' : ' 同学'); + if (state.user.role === 'ADMIN') { + adminNavBtn.classList.remove('hidden'); + registrationsNavBtn.classList.remove('hidden'); + // 隐藏个人信息和运动会报名菜单 + document.querySelector('[data-view="profile"]').classList.add('hidden'); + document.querySelector('[data-view="events"]').classList.add('hidden'); + // 自动切换到管理员后台视图 + switchView('admin'); + } + renderProfile(); + loadEventData(); + loadAdminData(); + }, + error: function () { + window.location.href = './login.html'; + } + }); + } + + navItems.forEach(function (item) { + item.addEventListener('click', function () { + switchView(item.getAttribute('data-view')); + }); + }); + + 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(); + switchView('profile'); +}); diff --git a/assets/js/common.js b/assets/js/common.js new file mode 100644 index 0000000..8ebb044 --- /dev/null +++ b/assets/js/common.js @@ -0,0 +1,71 @@ +(function () { + function resolveApiBase() { + if (window.location.protocol === 'file:') { + return 'http://localhost:8080'; + } + return window.location.protocol + '//' + window.location.hostname + ':8080'; + } + + var API_BASE = resolveApiBase(); + + function buildUrl(url) { + if (/^https?:\/\//.test(url)) { + return url; + } + return API_BASE + url; + } + + function ajax(options) { + var xhr = new XMLHttpRequest(); + xhr.open(options.method || 'GET', buildUrl(options.url), true); + xhr.withCredentials = true; + xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); + xhr.onreadystatechange = function () { + var response; + if (xhr.readyState !== 4) { + return; + } + try { + response = xhr.responseText ? JSON.parse(xhr.responseText) : {}; + } catch (error) { + response = { success: false, message: '响应数据解析失败' }; + } + if (xhr.status >= 200 && xhr.status < 300) { + options.success && options.success(response); + return; + } + options.error && options.error(xhr, response); + }; + xhr.onerror = function () { + options.error && options.error(xhr, { success: false, message: '网络异常,请确认后端已启动' }); + }; + xhr.send(options.data ? JSON.stringify(options.data) : null); + } + + function showMessage(element, message, isSuccess) { + if (!element) { + return; + } + element.textContent = message || ''; + element.className = 'form-message ' + (isSuccess ? 'success' : 'error'); + } + + function redirectIfLoggedIn() { + ajax({ + method: 'GET', + url: '/api/users/me', + success: function (response) { + if (response.success && response.data) { + window.location.href = './app.html'; + } + } + }); + } + + window.appUtils = { + ajax: ajax, + showMessage: showMessage, + redirectIfLoggedIn: redirectIfLoggedIn, + apiBase: API_BASE + }; +})(); diff --git a/assets/js/login.js b/assets/js/login.js new file mode 100644 index 0000000..02489d9 --- /dev/null +++ b/assets/js/login.js @@ -0,0 +1,55 @@ +document.addEventListener('DOMContentLoaded', function () { + appUtils.redirectIfLoggedIn(); + + var form = document.getElementById('loginForm'); + var message = document.getElementById('loginMessage'); + var goRegister = document.getElementById('goRegister'); + var usernameInput = document.querySelector('input[name="username"]'); + var passwordInput = document.querySelector('input[name="password"]'); + + // 强制清空输入字段,确保不保留历史登录信息 + setTimeout(function() { + usernameInput.value = ''; + passwordInput.value = ''; + // 移除可能的自动填充样式 + usernameInput.style.background = 'rgba(255, 255, 255, 0.95)'; + passwordInput.style.background = 'rgba(255, 255, 255, 0.95)'; + }, 100); + + // 再次清空,确保浏览器自动填充后也能被清空 + setTimeout(function() { + usernameInput.value = ''; + passwordInput.value = ''; + }, 500); + + goRegister.addEventListener('click', function () { + window.location.href = './register.html'; + }); + + form.addEventListener('submit', function (event) { + event.preventDefault(); + var formData = new FormData(form); + + appUtils.ajax({ + method: 'POST', + url: '/api/auth/login', + data: { + username: String(formData.get('username') || '').trim(), + password: String(formData.get('password') || '').trim() + }, + success: function (response) { + if (!response.success) { + appUtils.showMessage(message, response.message || '登录失败', false); + return; + } + appUtils.showMessage(message, '登录成功,正在进入系统...', true); + setTimeout(function () { + window.location.href = './app.html'; + }, 400); + }, + error: function (xhr, response) { + appUtils.showMessage(message, (response && response.message) || '网络异常,请确认后端已启动', false); + } + }); + }); +}); diff --git a/assets/js/register.js b/assets/js/register.js new file mode 100644 index 0000000..71ea933 --- /dev/null +++ b/assets/js/register.js @@ -0,0 +1,45 @@ +document.addEventListener('DOMContentLoaded', function () { + appUtils.redirectIfLoggedIn(); + + var form = document.getElementById('registerForm'); + var message = document.getElementById('registerMessage'); + var backLogin = document.getElementById('backLogin'); + + backLogin.addEventListener('click', function () { + window.location.href = './login.html'; + }); + + form.addEventListener('submit', function (event) { + event.preventDefault(); + var formData = new FormData(form); + + appUtils.ajax({ + method: 'POST', + url: '/api/auth/register', + data: { + idCard: String(formData.get('idCard') || '').trim(), + username: String(formData.get('username') || '').trim(), + password: String(formData.get('password') || '').trim(), + confirmPassword: String(formData.get('confirmPassword') || '').trim(), + name: String(formData.get('name') || '').trim(), + phone: String(formData.get('phone') || '').trim(), + gender: String(formData.get('gender') || ''), + college: String(formData.get('college') || '').trim(), + category: String(formData.get('category') || '') + }, + success: function (response) { + if (!response.success) { + appUtils.showMessage(message, response.message || '注册失败', false); + return; + } + appUtils.showMessage(message, '注册成功,正在进入系统...', true); + setTimeout(function () { + window.location.href = './app.html'; + }, 400); + }, + error: function (xhr, response) { + appUtils.showMessage(message, (response && response.message) || '网络异常,请确认后端已启动', false); + } + }); + }); +}); diff --git a/login.html b/login.html new file mode 100644 index 0000000..11b48da --- /dev/null +++ b/login.html @@ -0,0 +1,53 @@ + + + + + + + 运动会报名系统 - 登录 + + + + +
+
+
Campus Sports
+

运动会报名系统

+

统一完成赛事报名、个人信息维护与报名记录查看,界面简洁,流程顺畅。

+
+

报名须知

+
    +
  • 请使用真实身份信息注册,身份证号在系统中唯一。
  • +
  • 报名成功后可在“报名信息”中查看已选项目。
  • +
  • 如项目名额已满,将无法继续报名该项目。
  • +
+
+
+ +
+
+

账号登录

+

请输入账号和密码进入系统。

+
+
+ + +
+ + +
+

+
+
+
+ + + + + \ No newline at end of file diff --git a/register.html b/register.html new file mode 100644 index 0000000..1ea119d --- /dev/null +++ b/register.html @@ -0,0 +1,100 @@ + + + + + + + 运动会报名系统 - 注册 + + + + +
+
+
+

新用户注册

+

请完整填写个人信息,注册成功后将自动登录。

+
+
+ + + + + + + + + +
+ + +
+

+
+
+
+
Join The Meet
+

青春赛场,即刻出发

+

注册后可在线报名项目、维护个人资料,并查看所有已报名赛事。

+
+
+ + + + + \ No newline at end of file