commit
e1ccdd140b
10 changed files with 1661 additions and 0 deletions
@ -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 |
|||
@ -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 服务已启动 |
|||
- 修改配置文件后需要重启服务才能生效 |
|||
@ -0,0 +1,51 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-CN"> |
|||
|
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>运动会报名系统</title> |
|||
<link rel="stylesheet" href="./assets/css/style.css"> |
|||
</head> |
|||
|
|||
<body class="app-body"> |
|||
<div class="app-layout"> |
|||
<header class="topbar"> |
|||
<div> |
|||
<p class="topbar-tag">Sports Meet Workspace</p> |
|||
<h1>运动会报名系统</h1> |
|||
</div> |
|||
<div class="topbar-user"> |
|||
<span id="currentUserName">加载中...</span> |
|||
<button type="button" class="ghost-btn small" id="logoutBtn">退出登录</button> |
|||
</div> |
|||
</header> |
|||
|
|||
<main class="main-layout"> |
|||
<aside class="sidebar"> |
|||
<button class="nav-item active" data-view="profile">个人信息</button> |
|||
<button class="nav-item" data-view="events">运动会报名</button> |
|||
<button class="nav-item hidden" data-view="admin" id="adminNavBtn">管理员后台</button> |
|||
<button class="nav-item hidden" data-view="registrations" id="registrationsNavBtn">报名总览</button> |
|||
</aside> |
|||
|
|||
<section class="content-area"> |
|||
<div class="content-header"> |
|||
<div> |
|||
<h2 id="sectionTitle">个人信息</h2> |
|||
<p id="sectionDesc">查看并维护当前登录人员的基础资料。</p> |
|||
</div> |
|||
<div class="content-tabs hidden" id="subTabs"></div> |
|||
</div> |
|||
<section id="profileView" class="content-card"></section> |
|||
<section id="eventsView" class="content-card hidden"></section> |
|||
<section id="adminView" class="content-card hidden"></section> |
|||
<section id="registrationsView" class="content-card hidden"></section> |
|||
</section> |
|||
</main> |
|||
</div> |
|||
<script src="./assets/js/common.js"></script> |
|||
<script src="./assets/js/app.js"></script> |
|||
</body> |
|||
|
|||
</html> |
|||
@ -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,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="%236b7280" d="M7 10l5 5 5-5z"/></svg>'); |
|||
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; |
|||
} |
|||
} |
|||
@ -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, '"') |
|||
.replace(/'/g, '''); |
|||
} |
|||
|
|||
function buildOptions(list, current) { |
|||
return list.map(function (item) { |
|||
return '<option value="' + item + '"' + (item === current ? ' selected' : '') + '>' + item + '</option>'; |
|||
}).join(''); |
|||
} |
|||
|
|||
function buildCollegeOptions(current) { |
|||
var colleges = [ |
|||
'文学与文化传播学院', |
|||
'马克思主义学院', |
|||
'教育学院', |
|||
'外国语学院', |
|||
'历史文化学院', |
|||
'商学院', |
|||
'化学工程与技术学院', |
|||
'电子信息与电气工程学院', |
|||
'数学与统计学院', |
|||
'生物工程与技术学院', |
|||
'机电工程学院', |
|||
'土木工程学院', |
|||
'资源与环境工程学院', |
|||
'体育学院', |
|||
'美术与设计学院', |
|||
'音乐舞蹈学院', |
|||
'卫生健康学院', |
|||
'继续教育学院(培训中心)' |
|||
]; |
|||
return '<option value="">请选择学院</option>' + colleges.map(function (item) { |
|||
return '<option value="' + item + '"' + (item === current ? ' selected' : '') + '>' + item + '</option>'; |
|||
}).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 = '<div class="pagination">'; |
|||
html += '<button class="page-btn" data-page="prev" ' + (currentPage <= 1 ? 'disabled' : '') + '>上一页</button>'; |
|||
for (var i = 1; i <= totalPages; i++) { |
|||
html += '<button class="page-btn ' + (i === currentPage ? 'active' : '') + '" data-page="' + i + '">' + i + '</button>'; |
|||
} |
|||
html += '<button class="page-btn" data-page="next" ' + (currentPage >= totalPages ? 'disabled' : '') + '>下一页</button>'; |
|||
html += '</div>'; |
|||
return html; |
|||
} |
|||
|
|||
function renderTabs() { |
|||
var html = ''; |
|||
if (state.currentView === 'events') { |
|||
html = '' |
|||
+ '<button class="tab-item ' + (state.currentTab === 'all' ? 'active' : '') + '" data-tab="all">报名</button>' |
|||
+ '<button class="tab-item ' + (state.currentTab === 'mine' ? 'active' : '') + '" data-tab="mine">报名信息</button>'; |
|||
} else if (state.currentView === 'admin') { |
|||
html = '<button class="tab-item active" data-tab="users">用户信息</button>'; |
|||
} |
|||
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 = '' |
|||
+ '<div class="section-block">' |
|||
+ ' <div class="section-head"><div><h3>基础资料</h3><p class="info-meta">支持在线更新姓名、电话、性别、学院和类别信息。</p></div></div>' |
|||
+ ' <div class="profile-grid">' |
|||
+ ' <div class="info-card"><strong>身份证号</strong><p>' + escapeHtml(state.user.idCard) + '</p></div>' |
|||
+ ' <div class="info-card"><strong>登录账号</strong><p>' + escapeHtml(state.user.username) + '</p></div>' |
|||
+ ' <div class="info-card"><strong>姓名</strong><p>' + escapeHtml(state.user.name) + '</p></div>' |
|||
+ ' <div class="info-card"><strong>联系电话</strong><p>' + escapeHtml(state.user.phone) + '</p></div>' |
|||
+ ' <div class="info-card"><strong>性别</strong><p>' + escapeHtml(state.user.gender) + '</p></div>' |
|||
+ ' <div class="info-card"><strong>学院</strong><p>' + escapeHtml(state.user.college) + '</p></div>' |
|||
+ ' <div class="info-card"><strong>类别</strong><p>' + escapeHtml(state.user.category) + '</p></div>' |
|||
+ ' </div>' |
|||
+ '</div>' |
|||
+ '<div class="section-block">' |
|||
+ ' <div class="section-head"><div><h3>修改信息</h3><p class="info-meta">请保持信息真实准确,方便赛事组织和通知。</p></div></div>' |
|||
+ ' <form id="profileForm" class="form-grid two-columns">' |
|||
+ ' <label class="field"><span>姓名</span><input name="name" value="' + escapeHtml(state.user.name) + '"></label>' |
|||
+ ' <label class="field"><span>电话</span><input name="phone" value="' + escapeHtml(state.user.phone) + '"></label>' |
|||
+ ' <label class="field"><span>性别</span><select name="gender">' + buildOptions(['男', '女'], state.user.gender) + '</select></label>' |
|||
+ ' <label class="field"><span>学院</span><select name="college">' + buildCollegeOptions(state.user.college) + '</select></label>' |
|||
+ ' <label class="field full-row"><span>类别</span><select name="category">' + buildOptions(['青年组', '老年组'], state.user.category) + '</select></label>' |
|||
+ ' <div class="form-actions full-row"><button type="submit" class="primary-btn">保存修改</button></div>' |
|||
+ ' <p id="profileMessage" class="form-message full-row"></p>' |
|||
+ ' </form>' |
|||
+ '</div>'; |
|||
|
|||
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 '<div class="empty-state"><h3>暂无数据</h3><p>' + (isMine ? '你还没有报名任何项目。' : '当前暂无可报名项目。') + '</p></div>'; |
|||
} |
|||
var currentPage = isMine ? state.myEventPage : state.eventPage; |
|||
var totalPages = getTotalPages(list); |
|||
var paginatedList = getPaginatedList(list, currentPage); |
|||
|
|||
var html = '' |
|||
+ '<table class="event-table">' |
|||
+ ' <thead><tr><th>项目名称</th><th>项目类别</th><th>比赛时间</th><th>比赛地点</th><th>报名情况</th><th>项目说明</th><th>操作</th></tr></thead>' |
|||
+ ' <tbody>' |
|||
+ paginatedList.map(function (item) { |
|||
var actionHtml = isMine |
|||
? '<button class="action-btn cancel-btn" data-id="' + item.id + '">取消报名</button>' |
|||
: '<button class="action-btn register-btn" data-id="' + item.id + '" ' + (item.registered ? 'disabled' : '') + '>' + (item.registered ? '已报名' : '报名') + '</button>'; |
|||
return '' |
|||
+ '<tr>' |
|||
+ '<td>' + escapeHtml(item.eventName) + '</td>' |
|||
+ '<td>' + escapeHtml(item.eventCategory) + '</td>' |
|||
+ '<td>' + escapeHtml(item.eventTime) + '</td>' |
|||
+ '<td>' + escapeHtml(item.location) + '</td>' |
|||
+ '<td>' + escapeHtml(item.registeredCount + '/' + item.quota) + '</td>' |
|||
+ '<td>' + escapeHtml(item.description) + '</td>' |
|||
+ '<td>' + actionHtml + '</td>' |
|||
+ '</tr>'; |
|||
}).join('') |
|||
+ ' </tbody>' |
|||
+ '</table>'; |
|||
|
|||
html += renderPagination(totalPages, currentPage, isMine); |
|||
html += '<div class="page-info">第 ' + currentPage + ' 页 / 共 ' + totalPages + ' 页,共 ' + list.length + ' 条记录</div>'; |
|||
return html; |
|||
} |
|||
|
|||
function renderUserTable() { |
|||
if (!state.adminUsers.length) { |
|||
return '<div class="empty-state"><h3>暂无用户</h3><p>当前系统还没有用户数据。</p></div>'; |
|||
} |
|||
return '' |
|||
+ '<table class="event-table">' |
|||
+ '<thead><tr><th>姓名</th><th>账号</th><th>身份证号</th><th>电话</th><th>性别</th><th>学院</th><th>类别</th><th>角色</th><th>操作</th></tr></thead>' |
|||
+ '<tbody>' |
|||
+ state.adminUsers.map(function (item) { |
|||
return '' |
|||
+ '<tr>' |
|||
+ '<td>' + escapeHtml(item.name) + '</td>' |
|||
+ '<td>' + escapeHtml(item.username) + '</td>' |
|||
+ '<td>' + escapeHtml(item.idCard) + '</td>' |
|||
+ '<td>' + escapeHtml(item.phone) + '</td>' |
|||
+ '<td>' + escapeHtml(item.gender) + '</td>' |
|||
+ '<td>' + escapeHtml(item.college) + '</td>' |
|||
+ '<td>' + escapeHtml(item.category) + '</td>' |
|||
+ '<td>' + escapeHtml(item.role === 'ADMIN' ? '管理员' : '普通用户') + '</td>' |
|||
+ '<td>' |
|||
+ '<button class="action-btn reset-btn" data-id="' + item.id + '">重置密码</button> ' |
|||
+ '<button class="action-btn delete-btn" data-id="' + item.id + '">删除账号</button>' |
|||
+ '</td>' |
|||
+ '</tr>'; |
|||
}).join('') |
|||
+ '</tbody></table>'; |
|||
} |
|||
|
|||
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 '<div class="empty-state"><h3>暂无报名记录</h3><p>目前还没有用户完成项目报名。</p></div>'; |
|||
} |
|||
return '' |
|||
+ '<table class="event-table">' |
|||
+ '<thead><tr><th>姓名</th><th>账号</th><th>电话</th><th>学院</th><th>类别</th><th>项目名称</th><th>项目类别</th><th>时间地点</th><th>状态</th><th>报名时间</th></tr></thead>' |
|||
+ '<tbody>' |
|||
+ paginatedRegistrations.map(function (item) { |
|||
return '' |
|||
+ '<tr>' |
|||
+ '<td>' + escapeHtml(item.studentName) + '</td>' |
|||
+ '<td>' + escapeHtml(item.username) + '</td>' |
|||
+ '<td>' + escapeHtml(item.phone) + '</td>' |
|||
+ '<td>' + escapeHtml(item.college) + '</td>' |
|||
+ '<td>' + escapeHtml(item.category) + '</td>' |
|||
+ '<td>' + escapeHtml(item.eventName) + '</td>' |
|||
+ '<td>' + escapeHtml(item.eventCategory) + '</td>' |
|||
+ '<td>' + escapeHtml(item.eventTime + ' / ' + item.location) + '</td>' |
|||
+ '<td>' + escapeHtml(item.status) + '</td>' |
|||
+ '<td>' + escapeHtml(item.createdAt) + '</td>' |
|||
+ '</tr>'; |
|||
}).join('') |
|||
+ '</tbody></table>'; |
|||
} |
|||
|
|||
function renderAdminContent() { |
|||
adminView.innerHTML = '' |
|||
+ '<div class="summary-grid">' |
|||
+ ' <div class="summary-card"><strong>用户总数</strong><p>' + state.adminUsers.length + '</p></div>' |
|||
+ ' <div class="summary-card"><strong>管理员数量</strong><p>' + state.adminUsers.filter(function (item) { return item.role === "ADMIN"; }).length + '</p></div>' |
|||
+ ' <div class="summary-card"><strong>普通用户数量</strong><p>' + state.adminUsers.filter(function (item) { return item.role !== "ADMIN"; }).length + '</p></div>' |
|||
+ '</div>' |
|||
+ 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 = '' |
|||
+ '<div class="summary-grid">' |
|||
+ ' <div class="summary-card"><strong>报名记录总数</strong><p>' + state.adminRegistrations.length + '</p></div>' |
|||
+ ' <div class="summary-card"><strong>已报名状态</strong><p>' + state.adminRegistrations.filter(function (item) { return item.status === "已报名"; }).length + '</p></div>' |
|||
+ ' <div class="summary-card"><strong>覆盖项目数</strong><p>' + uniqueEventCount() + '</p></div>' |
|||
+ '</div>' |
|||
+ 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 = '' |
|||
+ '<div class="summary-grid">' |
|||
+ ' <div class="summary-card"><strong>报名记录总数</strong><p>' + state.adminRegistrations.length + '</p></div>' |
|||
+ ' <div class="summary-card"><strong>已报名状态</strong><p>' + state.adminRegistrations.filter(function (item) { return item.status === "已报名"; }).length + '</p></div>' |
|||
+ ' <div class="summary-card"><strong>覆盖项目数</strong><p>' + uniqueEventCount() + '</p></div>' |
|||
+ '</div>' |
|||
+ 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'); |
|||
}); |
|||
@ -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 |
|||
}; |
|||
})(); |
|||
@ -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); |
|||
} |
|||
}); |
|||
}); |
|||
}); |
|||
@ -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); |
|||
} |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,53 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-CN"> |
|||
|
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>运动会报名系统 - 登录</title> |
|||
<link rel="stylesheet" href="./assets/css/style.css"> |
|||
</head> |
|||
|
|||
<body class="auth-page"> |
|||
<div class="auth-shell login-shell"> |
|||
<section class="auth-hero"> |
|||
<div class="auth-badge">Campus Sports</div> |
|||
<h1>运动会报名系统</h1> |
|||
<p class="auth-subtitle">统一完成赛事报名、个人信息维护与报名记录查看,界面简洁,流程顺畅。</p> |
|||
<div class="notice-card"> |
|||
<h2>报名须知</h2> |
|||
<ul> |
|||
<li>请使用真实身份信息注册,身份证号在系统中唯一。</li> |
|||
<li>报名成功后可在“报名信息”中查看已选项目。</li> |
|||
<li>如项目名额已满,将无法继续报名该项目。</li> |
|||
</ul> |
|||
</div> |
|||
</section> |
|||
|
|||
<section class="auth-panel"> |
|||
<div class="panel-head"> |
|||
<h2>账号登录</h2> |
|||
<p>请输入账号和密码进入系统。</p> |
|||
</div> |
|||
<form id="loginForm" class="form-grid single-column" autocomplete="off"> |
|||
<label class="field"> |
|||
<span>登录账号</span> |
|||
<input type="text" name="username" placeholder="请输入登录账号" autocomplete="off"> |
|||
</label> |
|||
<label class="field"> |
|||
<span>登录密码</span> |
|||
<input type="password" name="password" placeholder="请输入登录密码" autocomplete="off"> |
|||
</label> |
|||
<div class="form-actions stacked"> |
|||
<button type="submit" class="primary-btn">登录</button> |
|||
<button type="button" class="ghost-btn" id="goRegister">注册</button> |
|||
</div> |
|||
<p id="loginMessage" class="form-message"></p> |
|||
</form> |
|||
</section> |
|||
</div> |
|||
<script src="./assets/js/common.js"></script> |
|||
<script src="./assets/js/login.js"></script> |
|||
</body> |
|||
|
|||
</html> |
|||
@ -0,0 +1,100 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-CN"> |
|||
|
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>运动会报名系统 - 注册</title> |
|||
<link rel="stylesheet" href="./assets/css/style.css"> |
|||
</head> |
|||
|
|||
<body class="auth-page register-page"> |
|||
<div class="auth-shell register-shell"> |
|||
<section class="auth-panel wide-panel"> |
|||
<div class="panel-head"> |
|||
<h2>新用户注册</h2> |
|||
<p>请完整填写个人信息,注册成功后将自动登录。</p> |
|||
</div> |
|||
<form id="registerForm" class="form-grid two-columns"> |
|||
<label class="field"> |
|||
<span>身份证号</span> |
|||
<input type="text" name="idCard" placeholder="请输入身份证号"> |
|||
</label> |
|||
<label class="field"> |
|||
<span>登录账号</span> |
|||
<input type="text" name="username" placeholder="请输入登录账号"> |
|||
</label> |
|||
<label class="field"> |
|||
<span>密码</span> |
|||
<input type="password" name="password" placeholder="请输入密码"> |
|||
</label> |
|||
<label class="field"> |
|||
<span>确认密码</span> |
|||
<input type="password" name="confirmPassword" placeholder="请再次输入密码"> |
|||
</label> |
|||
<label class="field"> |
|||
<span>姓名</span> |
|||
<input type="text" name="name" placeholder="请输入姓名"> |
|||
</label> |
|||
<label class="field"> |
|||
<span>电话</span> |
|||
<input type="text" name="phone" placeholder="请输入联系电话"> |
|||
</label> |
|||
<label class="field"> |
|||
<span>性别</span> |
|||
<select name="gender"> |
|||
<option value="">请选择性别</option> |
|||
<option value="男">男</option> |
|||
<option value="女">女</option> |
|||
</select> |
|||
</label> |
|||
<label class="field"> |
|||
<span>学院</span> |
|||
<select name="college" required> |
|||
<option value="">请选择学院</option> |
|||
<option value="文学与文化传播学院">文学与文化传播学院</option> |
|||
<option value="马克思主义学院">马克思主义学院</option> |
|||
<option value="教育学院">教育学院</option> |
|||
<option value="外国语学院">外国语学院</option> |
|||
<option value="历史文化学院">历史文化学院</option> |
|||
<option value="商学院">商学院</option> |
|||
<option value="化学工程与技术学院">化学工程与技术学院</option> |
|||
<option value="电子信息与电气工程学院">电子信息与电气工程学院</option> |
|||
<option value="数学与统计学院">数学与统计学院</option> |
|||
<option value="生物工程与技术学院">生物工程与技术学院</option> |
|||
<option value="机电工程学院">机电工程学院</option> |
|||
<option value="土木工程学院">土木工程学院</option> |
|||
<option value="资源与环境工程学院">资源与环境工程学院</option> |
|||
<option value="体育学院">体育学院</option> |
|||
<option value="美术与设计学院">美术与设计学院</option> |
|||
<option value="音乐舞蹈学院">音乐舞蹈学院</option> |
|||
<option value="卫生健康学院">卫生健康学院</option> |
|||
<option value="继续教育学院(培训中心)">继续教育学院(培训中心)</option> |
|||
</select> |
|||
</label> |
|||
<label class="field full-row"> |
|||
<span>类别</span> |
|||
<select name="category"> |
|||
<option value="">请选择类别</option> |
|||
<option value="青年组">青年组</option> |
|||
<option value="老年组">老年组</option> |
|||
</select> |
|||
</label> |
|||
<div class="form-actions full-row"> |
|||
<button type="submit" class="primary-btn">提交注册</button> |
|||
<button type="button" class="ghost-btn" id="backLogin">返回登录</button> |
|||
</div> |
|||
<p id="registerMessage" class="form-message full-row"></p> |
|||
</form> |
|||
</section> |
|||
<section class="register-side-card"> |
|||
<div class="auth-badge">Join The Meet</div> |
|||
<h1>青春赛场,即刻出发</h1> |
|||
<p>注册后可在线报名项目、维护个人资料,并查看所有已报名赛事。</p> |
|||
</section> |
|||
</div> |
|||
<script src="./assets/js/common.js"></script> |
|||
<script src="./assets/js/register.js"></script> |
|||
</body> |
|||
|
|||
</html> |
|||
Loading…
Reference in new issue