193 lines
6.6 KiB
HTML
193 lines
6.6 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="zh-CN">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>人脸识别后台管理</title>
|
||
|
|
<style>
|
||
|
|
body {
|
||
|
|
font-family: Arial, sans-serif;
|
||
|
|
max-width: 800px;
|
||
|
|
margin: 0 auto;
|
||
|
|
padding: 20px;
|
||
|
|
}
|
||
|
|
.card {
|
||
|
|
border: 1px solid #ddd;
|
||
|
|
padding: 20px;
|
||
|
|
margin-bottom: 20px;
|
||
|
|
border-radius: 8px;
|
||
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
|
|
}
|
||
|
|
.form-group {
|
||
|
|
margin-bottom: 15px;
|
||
|
|
}
|
||
|
|
label {
|
||
|
|
display: block;
|
||
|
|
margin-bottom: 5px;
|
||
|
|
font-weight: bold;
|
||
|
|
}
|
||
|
|
input[type="text"],
|
||
|
|
input[type="file"] {
|
||
|
|
width: 100%;
|
||
|
|
padding: 8px;
|
||
|
|
box-sizing: border-box;
|
||
|
|
}
|
||
|
|
button {
|
||
|
|
background-color: #007bff;
|
||
|
|
color: white;
|
||
|
|
border: none;
|
||
|
|
padding: 10px 20px;
|
||
|
|
cursor: pointer;
|
||
|
|
border-radius: 4px;
|
||
|
|
}
|
||
|
|
button:hover {
|
||
|
|
background-color: #0056b3;
|
||
|
|
}
|
||
|
|
table {
|
||
|
|
width: 100%;
|
||
|
|
border-collapse: collapse;
|
||
|
|
margin-top: 10px;
|
||
|
|
}
|
||
|
|
th,
|
||
|
|
td {
|
||
|
|
border: 1px solid #ddd;
|
||
|
|
padding: 8px;
|
||
|
|
text-align: left;
|
||
|
|
}
|
||
|
|
th {
|
||
|
|
background-color: #f2f2f2;
|
||
|
|
}
|
||
|
|
img.avatar {
|
||
|
|
width: 50px;
|
||
|
|
height: 50px;
|
||
|
|
object-fit: cover;
|
||
|
|
border-radius: 50%;
|
||
|
|
}
|
||
|
|
.status {
|
||
|
|
margin-top: 10px;
|
||
|
|
padding: 10px;
|
||
|
|
display: none;
|
||
|
|
}
|
||
|
|
.success {
|
||
|
|
background-color: #d4edda;
|
||
|
|
color: #155724;
|
||
|
|
}
|
||
|
|
.error {
|
||
|
|
background-color: #f8d7da;
|
||
|
|
color: #721c24;
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<h1>人脸识别后台管理系统</h1>
|
||
|
|
<div class="card">
|
||
|
|
<h2>用户注册</h2>
|
||
|
|
<div class="form-group">
|
||
|
|
<label>姓名:</label>
|
||
|
|
<input type="text" id="nameInput" placeholder="请输入姓名">
|
||
|
|
</div>
|
||
|
|
<div class="form-group">
|
||
|
|
<label>照片:</label>
|
||
|
|
<input type="file" id="photoInput" accept="image/*">
|
||
|
|
</div>
|
||
|
|
<button onclick="registerUser()">注册用户</button>
|
||
|
|
<div id="registerStatus" class="status"></div>
|
||
|
|
</div>
|
||
|
|
<div class="card">
|
||
|
|
<h2>用户列表 <button onclick="loadUsers()" style="float:right; padding: 5px 10px; font-size: 12px;">刷新</button></h2>
|
||
|
|
<table id="userTable">
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>ID</th>
|
||
|
|
<th>头像</th>
|
||
|
|
<th>姓名</th>
|
||
|
|
<th>特征状态</th>
|
||
|
|
<th>操作</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<!-- 用户列表将在这里渲染 -->
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
<script>
|
||
|
|
const API_BASE = '/api/users';
|
||
|
|
// 页面加载时获取用户列表
|
||
|
|
window.onload = loadUsers;
|
||
|
|
async function registerUser() {
|
||
|
|
const name = document.getElementById('nameInput').value;
|
||
|
|
const photoFile = document.getElementById('photoInput').files[0];
|
||
|
|
const statusDiv = document.getElementById('registerStatus');
|
||
|
|
if (!name || !photoFile) {
|
||
|
|
showStatus(statusDiv, '请填写姓名并选择照片', 'error');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const formData = new FormData();
|
||
|
|
formData.append('name', name);
|
||
|
|
formData.append('photo', photoFile);
|
||
|
|
try {
|
||
|
|
showStatus(statusDiv, '正在注册...', 'success');
|
||
|
|
const response = await fetch(`${API_BASE}/add`, {
|
||
|
|
method: 'POST',
|
||
|
|
body: formData
|
||
|
|
});
|
||
|
|
const result = await response.json();
|
||
|
|
if (result.code === 200) {
|
||
|
|
showStatus(statusDiv, `注册成功: ${result.data.name}`, 'success');
|
||
|
|
document.getElementById('nameInput').value = '';
|
||
|
|
document.getElementById('photoInput').value = '';
|
||
|
|
loadUsers(); // 刷新列表
|
||
|
|
} else {
|
||
|
|
showStatus(statusDiv, `注册失败: ${result.msg}`, 'error');
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
showStatus(statusDiv, `请求错误: ${error.message}`, 'error');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
async function loadUsers() {
|
||
|
|
try {
|
||
|
|
const response = await fetch(`${API_BASE}/list`);
|
||
|
|
const result = await response.json();
|
||
|
|
if (result.code === 200) {
|
||
|
|
const tbody = document.querySelector('#userTable tbody');
|
||
|
|
tbody.innerHTML = '';
|
||
|
|
result.data.forEach(user => {
|
||
|
|
const hasFeature = user.featureData && user.featureData.length > 10;
|
||
|
|
const row = `
|
||
|
|
<tr>
|
||
|
|
<td>${user.id}</td>
|
||
|
|
<td>${user.photoUrl ? `<img src="/uploads/${user.photoUrl}" class="avatar" onerror="this.src='https://via.placeholder.com/50'">` : '无'}</td>
|
||
|
|
<td>${user.name}</td>
|
||
|
|
<td>${hasFeature ? '✅ 已提取' : '❌ 无特征'}</td>
|
||
|
|
<td><button onclick="deleteUser(${user.id})" style="background-color: #dc3545; padding: 5px 10px;">删除</button></td>
|
||
|
|
</tr>
|
||
|
|
`;
|
||
|
|
tbody.innerHTML += row;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('加载用户列表失败:', error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
async function deleteUser(id) {
|
||
|
|
if (!confirm('确定要删除该用户吗?')) return;
|
||
|
|
try {
|
||
|
|
const response = await fetch(`${API_BASE}/${id}`, { method: 'DELETE' });
|
||
|
|
const result = await response.json();
|
||
|
|
if (result.code === 200) {
|
||
|
|
loadUsers();
|
||
|
|
} else {
|
||
|
|
alert('删除失败: ' + result.msg);
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
alert('请求错误: ' + error.message);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function showStatus(element, message, type) {
|
||
|
|
element.textContent = message;
|
||
|
|
element.className = `status ${type}`;
|
||
|
|
element.style.display = 'block';
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
</html>
|