Files
springboot-scaffold/target/classes/static/users.html

278 lines
13 KiB
HTML
Raw Normal View History

<!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>