Week 1-8: Spring Boot 学习计划完整项目

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-29 23:45:17 +08:00
commit f95aa18724
201 changed files with 18595 additions and 0 deletions

View File

@@ -0,0 +1,210 @@
const BASE = '/api';
let token = localStorage.getItem('wk5_token') || '';
let role = localStorage.getItem('wk5_role') || '';
document.addEventListener('DOMContentLoaded', () => {
if (token) { showMain(); loadStudents(); checkRoleUI(); }
});
// ==================== 认证 ====================
async function doLogin() {
const username = document.getElementById('login-username').value.trim();
const password = document.getElementById('login-password').value.trim();
if (!username || !password) { showLoginError('请输入用户名和密码'); return; }
try {
const resp = await fetch(BASE + '/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const result = await resp.json();
if (resp.ok && result.code === 200) {
token = result.data.token;
role = result.data.role;
localStorage.setItem('wk5_token', token);
localStorage.setItem('wk5_role', role);
showMain(); loadStudents(); checkRoleUI();
} else {
showLoginError(result.message || '登录失败');
}
} catch (e) { showLoginError('连接失败: ' + e.message); }
}
async function doRegister() {
const username = document.getElementById('reg-username').value.trim();
const password = document.getElementById('reg-password').value.trim();
if (!username || !password) { document.getElementById('reg-error').textContent = '请输入用户名和密码'; return; }
try {
const resp = await fetch(BASE + '/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const result = await resp.json();
if (resp.ok && result.code === 200) {
token = result.data.token;
role = result.data.role;
localStorage.setItem('wk5_token', token);
localStorage.setItem('wk5_role', role);
showMain(); loadStudents(); checkRoleUI();
} else {
document.getElementById('reg-error').textContent = result.message || '注册失败';
}
} catch (e) { document.getElementById('reg-error').textContent = '连接失败'; }
}
function showLoginError(msg) { document.getElementById('login-error').textContent = msg; }
function showLogin() {
document.getElementById('login-panel').style.display = 'block';
document.getElementById('register-panel').style.display = 'none';
document.getElementById('main-panel').style.display = 'none';
}
function showRegister() {
document.getElementById('login-panel').style.display = 'none';
document.getElementById('register-panel').style.display = 'block';
}
function showMain() {
document.getElementById('login-panel').style.display = 'none';
document.getElementById('register-panel').style.display = 'none';
document.getElementById('main-panel').style.display = 'block';
}
function logout() {
localStorage.removeItem('wk5_token'); localStorage.removeItem('wk5_role');
token = ''; role = ''; showLogin();
}
/** 根据角色显示/隐藏操作按钮 */
function checkRoleUI() {
document.getElementById('user-info').textContent = '当前: ' + (role || '-') + ' | ';
document.getElementById('btn-add').style.display = (role === 'ADMIN') ? '' : 'none';
}
// ==================== 请求封装 ====================
async function api(url, options) {
options = options || {};
options.headers = options.headers || {};
options.headers['Authorization'] = 'Bearer ' + token;
const resp = await fetch(url, options);
const result = await resp.json();
if (resp.status === 403) {
showError('权限不足:需要 ADMIN 角色'); return null;
}
if ((resp.status === 401 || resp.status === 403) && result.code !== 200) {
logout(); showError('认证失败,请重新登录'); return null;
}
if (result.extra && result.extra.orm) {
document.getElementById('stat-orm').textContent = result.extra.orm;
}
return result;
}
function showError(msg) {
const bar = document.getElementById('error-bar');
bar.textContent = '⚠ ' + msg; bar.style.display = 'block';
setTimeout(() => bar.style.display = 'none', 4000);
}
// ==================== 数据 ====================
async function loadStudents(keyword) {
const tbody = document.getElementById('table-body');
tbody.innerHTML = '<tr><td colspan="6" class="loading">加载中...</td></tr>';
let url = BASE + '/students';
if (keyword) url += '?keyword=' + encodeURIComponent(keyword);
const result = await api(url);
if (!result) { tbody.innerHTML = '<tr><td colspan="6" class="empty">加载失败</td></tr>'; return; }
const students = result.data;
if (!students || students.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="empty">暂无数据</td></tr>';
} else {
tbody.innerHTML = students.map(s => {
let cls = 'badge-poor';
if (s.score >= 90) cls = 'badge-excellent';
else if (s.score >= 80) cls = 'badge-good';
else if (s.score >= 60) cls = 'badge-normal';
const actions = role === 'ADMIN'
? `<button class="btn-edit" onclick="showForm(${s.id})">编辑</button>
<button class="btn-danger" onclick="deleteStudent(${s.id})">删除</button>`
: '<span style="color:#ccc">--</span>';
return `<tr>
<td>${s.id}</td><td><strong>${escapeHtml(s.name)}</strong></td>
<td>${s.age}</td><td>${escapeHtml(s.email)}</td>
<td><span class="badge ${cls}">${s.score}</span></td>
<td>${actions}</td></tr>`;
}).join('');
}
document.getElementById('stat-total').textContent = result.extra?.total ?? students.length;
const statResp = await api(BASE + '/students/stats/excellent?min=85');
if (statResp) document.getElementById('stat-excellent').textContent = statResp.data;
}
function search() { loadStudents(document.getElementById('keyword').value.trim() || undefined); }
// ==================== 表单 ====================
async function showForm(id) {
document.getElementById('form-id').value = ''; document.getElementById('student-form').reset();
if (id) {
document.getElementById('modal-title').textContent = '编辑学生';
const result = await api(BASE + '/students/' + id);
if (!result) return;
const s = result.data;
document.getElementById('form-id').value = s.id;
document.getElementById('form-name').value = s.name;
document.getElementById('form-age').value = s.age;
document.getElementById('form-email').value = s.email;
document.getElementById('form-score').value = s.score;
} else {
document.getElementById('modal-title').textContent = '新增学生';
}
document.getElementById('modal-overlay').classList.add('show');
}
async function submitForm(e) {
e.preventDefault();
const id = document.getElementById('form-id').value;
const body = {
name: document.getElementById('form-name').value.trim(),
age: parseInt(document.getElementById('form-age').value),
email: document.getElementById('form-email').value.trim(),
score: parseInt(document.getElementById('form-score').value)
};
let result;
if (id) {
result = await api(BASE + '/students/' + id, {
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
});
} else {
result = await api(BASE + '/students', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
});
}
if (result) { closeModal(); loadStudents(); }
}
async function deleteStudent(id) {
if (!confirm('确认删除MP 模式下为逻辑删除)')) return;
const result = await api(BASE + '/students/' + id, { method: 'DELETE' });
if (result) loadStudents();
}
function closeModal() { document.getElementById('modal-overlay').classList.remove('show'); }
document.addEventListener('click', e => { if (e.target.id === 'modal-overlay') closeModal(); });
document.addEventListener('keydown', e => {
if (e.key === 'Enter' && document.getElementById('login-panel').style.display !== 'none') doLogin();
});
function escapeHtml(t) { const d = document.createElement('div'); d.textContent = t; return d.innerHTML; }