feat: Spring Boot 学习脚手架 v2.0
- 新增 IoC 容器学习模块 - 新增 AOP 切面编程学习模块 - 新增 MyBatis 集成学习模块 - 新增事务管理学习模块 - 新增用户/产品/订单 CRUD - 新增 7 个交互式学习页面 - 集成性能监控切面
This commit is contained in:
278
target/classes/static/users.html
Normal file
278
target/classes/static/users.html
Normal file
@@ -0,0 +1,278 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>用户管理 - Spring Boot CRUD</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; }
|
||||
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
|
||||
|
||||
.header { background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); color: white; padding: 30px 20px; text-align: center; margin-bottom: 20px; border-radius: 10px; }
|
||||
.header h1 { font-size: 2em; }
|
||||
|
||||
.nav { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; justify-content: center; }
|
||||
.nav a { padding: 10px 20px; background: white; border-radius: 20px; text-decoration: none; color: #333; font-size: 0.9em; }
|
||||
.nav a:hover, .nav a.active { background: #11998e; color: white; }
|
||||
|
||||
.card { background: white; border-radius: 10px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.08); }
|
||||
.card h3 { color: #11998e; margin-bottom: 15px; border-bottom: 2px solid #eee; padding-bottom: 10px; }
|
||||
|
||||
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; margin-bottom: 20px; }
|
||||
.form-group { display: flex; flex-direction: column; }
|
||||
.form-group label { margin-bottom: 5px; color: #666; font-size: 0.9em; }
|
||||
.form-group input, .form-group textarea { padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 1em; }
|
||||
.form-group input:focus, .form-group textarea:focus { outline: none; border-color: #11998e; }
|
||||
|
||||
.btn { padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 1em; margin: 5px; }
|
||||
.btn-primary { background: #11998e; color: white; }
|
||||
.btn-primary:hover { background: #0d7a6e; }
|
||||
.btn-secondary { background: #6c757d; color: white; }
|
||||
.btn-danger { background: #dc3545; color: white; }
|
||||
.btn-warning { background: #ffc107; color: #333; }
|
||||
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
|
||||
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
|
||||
th { background: #f8f9fa; font-weight: 600; }
|
||||
tr:hover { background: #f8f9fa; }
|
||||
.actions { display: flex; gap: 5px; }
|
||||
.actions button { padding: 5px 10px; font-size: 0.85em; }
|
||||
|
||||
.badge { padding: 4px 8px; border-radius: 4px; font-size: 0.8em; }
|
||||
.badge-active { background: #28a745; color: white; }
|
||||
.badge-inactive { background: #6c757d; color: white; }
|
||||
|
||||
.search-box { display: flex; gap: 10px; margin-bottom: 20px; }
|
||||
.search-box input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
|
||||
|
||||
.result { background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 5px; margin-top: 10px; font-family: monospace; font-size: 0.9em; overflow-x: auto; max-height: 300px; overflow-y: auto; }
|
||||
.result.success { background: #d4edda; color: #155724; }
|
||||
.result.error { background: #f8d7da; color: #721c24; }
|
||||
|
||||
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; }
|
||||
.modal-content { background: white; margin: 50px auto; padding: 30px; width: 90%; max-width: 600px; border-radius: 10px; }
|
||||
.modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||
.modal-header h3 { margin: 0; border: none; }
|
||||
.close { font-size: 1.5em; cursor: pointer; color: #999; }
|
||||
.close:hover { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>👥 用户管理</h1>
|
||||
<p>RESTful CRUD 操作演示</p>
|
||||
</div>
|
||||
|
||||
<div class="nav">
|
||||
<a href="index.html">🏠 首页</a>
|
||||
<a href="ioc.html">📦 IoC</a>
|
||||
<a href="aop.html">🔪 AOP</a>
|
||||
<a href="mybatis.html">💾 MyBatis</a>
|
||||
<a href="transaction.html">🔄 事务</a>
|
||||
<a href="users.html" class="active">👥 用户</a>
|
||||
<a href="api.html">🔌 API</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>➕ 创建/编辑用户</h3>
|
||||
<form id="userForm">
|
||||
<input type="hidden" id="userId">
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>用户名 *</label>
|
||||
<input type="text" id="username" required placeholder="输入用户名">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>邮箱 *</label>
|
||||
<input type="email" id="email" required placeholder="输入邮箱">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>手机号</label>
|
||||
<input type="text" id="phone" placeholder="输入手机号">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>状态</label>
|
||||
<select id="active" style="padding:10px;border:1px solid #ddd;border-radius:5px;">
|
||||
<option value="true">启用</option>
|
||||
<option value="false">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:15px;">
|
||||
<label>简介</label>
|
||||
<textarea id="bio" rows="3" placeholder="输入个人简介"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">💾 保存</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="resetForm()">🔄 重置</button>
|
||||
</form>
|
||||
<div id="formResult"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>📋 用户列表</h3>
|
||||
<div class="search-box">
|
||||
<input type="text" id="searchInput" placeholder="搜索用户名...">
|
||||
<button class="btn btn-primary" onclick="searchUsers()">🔍 搜索</button>
|
||||
<button class="btn btn-secondary" onclick="loadUsers()">🔄 刷新</button>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>用户名</th>
|
||||
<th>邮箱</th>
|
||||
<th>手机号</th>
|
||||
<th>状态</th>
|
||||
<th>创建时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="userTable">
|
||||
<tr><td colspan="7" style="text-align:center;color:#999;">加载中...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let users = [];
|
||||
|
||||
async function loadUsers() {
|
||||
const tbody = document.getElementById('userTable');
|
||||
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;color:#999;">加载中...</td></tr>';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/users');
|
||||
users = await res.json();
|
||||
renderUsers(users);
|
||||
} catch (e) {
|
||||
tbody.innerHTML = `<tr><td colspan="7" style="text-align:center;color:#dc3545;">加载失败: ${e.message}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderUsers(data) {
|
||||
const tbody = document.getElementById('userTable');
|
||||
if (data.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;color:#999;">暂无数据</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = data.map(u => `
|
||||
<tr>
|
||||
<td>${u.id}</td>
|
||||
<td>${u.username}</td>
|
||||
<td>${u.email}</td>
|
||||
<td>${u.phone || '-'}</td>
|
||||
<td><span class="badge ${u.active ? 'badge-active' : 'badge-inactive'}">${u.active ? '启用' : '禁用'}</span></td>
|
||||
<td>${u.createdAt ? new Date(u.createdAt).toLocaleString() : '-'}</td>
|
||||
<td class="actions">
|
||||
<button class="btn btn-warning" onclick="editUser(${u.id})">✏️ 编辑</button>
|
||||
<button class="btn btn-danger" onclick="deleteUser(${u.id})">🗑️ 删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
async function searchUsers() {
|
||||
const keyword = document.getElementById('searchInput').value.trim();
|
||||
if (!keyword) {
|
||||
loadUsers();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/users/search?username=${encodeURIComponent(keyword)}`);
|
||||
const data = await res.json();
|
||||
renderUsers(data);
|
||||
} catch (e) {
|
||||
alert('搜索失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('userForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const userId = document.getElementById('userId').value;
|
||||
const user = {
|
||||
username: document.getElementById('username').value,
|
||||
email: document.getElementById('email').value,
|
||||
phone: document.getElementById('phone').value,
|
||||
bio: document.getElementById('bio').value,
|
||||
active: document.getElementById('active').value === 'true'
|
||||
};
|
||||
|
||||
const resultDiv = document.getElementById('formResult');
|
||||
resultDiv.className = 'result';
|
||||
resultDiv.textContent = '保存中...';
|
||||
|
||||
try {
|
||||
const url = userId ? `/api/users/${userId}` : '/api/users';
|
||||
const method = userId ? 'PUT' : 'POST';
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(user)
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (res.ok) {
|
||||
resultDiv.className = 'result success';
|
||||
resultDiv.innerHTML = `<strong>✅ 保存成功!</strong><br>ID: ${data.id}, 用户名: ${data.username}`;
|
||||
resetForm();
|
||||
loadUsers();
|
||||
} else {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.innerHTML = `<strong>❌ 保存失败</strong><br>${JSON.stringify(data)}`;
|
||||
}
|
||||
} catch (e) {
|
||||
resultDiv.className = 'result error';
|
||||
resultDiv.innerHTML = `<strong>❌ 错误</strong><br>${e.message}`;
|
||||
}
|
||||
});
|
||||
|
||||
function editUser(id) {
|
||||
const user = users.find(u => u.id === id);
|
||||
if (!user) return;
|
||||
|
||||
document.getElementById('userId').value = user.id;
|
||||
document.getElementById('username').value = user.username;
|
||||
document.getElementById('email').value = user.email;
|
||||
document.getElementById('phone').value = user.phone || '';
|
||||
document.getElementById('bio').value = user.bio || '';
|
||||
document.getElementById('active').value = user.active.toString();
|
||||
|
||||
document.getElementById('formResult').innerHTML = '<span style="color:#11998e;">✏️ 正在编辑用户 #' + id + '</span>';
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
async function deleteUser(id) {
|
||||
if (!confirm(`确定要删除用户 #${id} 吗?`)) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/users/${id}`, { method: 'DELETE' });
|
||||
if (res.ok) {
|
||||
alert('✅ 删除成功');
|
||||
loadUsers();
|
||||
} else {
|
||||
alert('❌ 删除失败');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('删除失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
document.getElementById('userForm').reset();
|
||||
document.getElementById('userId').value = '';
|
||||
document.getElementById('formResult').innerHTML = '';
|
||||
}
|
||||
|
||||
// 初始化
|
||||
loadUsers();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user