feat: 后台管理添加用户管理功能 + 前端用户注册登录调用backend API

This commit is contained in:
2026-04-27 17:16:44 +08:00
parent 24ba04b3e3
commit e9357577cb
4 changed files with 646 additions and 80 deletions

View File

@@ -81,6 +81,9 @@ async function loadPage(page) {
case 'stats':
await loadStatsPage(content);
break;
case 'users':
await loadUsersPage(content);
break;
case 'llm':
await loadLLMPage(content);
break;
@@ -177,6 +180,222 @@ async function loadStatsPage(content) {
`;
}
// ==================== 用户管理页面 ====================
let users = [];
async function loadUsersPage(content) {
users = await fetchAPI('/api/admin/users');
content.innerHTML = `
<div class="content-header">
<h1 class="content-title">用户管理</h1>
<div style="display: flex; gap: 12px;">
<input type="text" class="form-input" id="userSearch" placeholder="搜索用户名/手机/邮箱" style="width: 200px;" onkeyup="searchUsers(event)">
<button class="add-btn" onclick="searchUsersBtn()">搜索</button>
</div>
</div>
<div class="stats-grid" style="grid-template-columns: repeat(4, 1fr);">
<div class="stat-card">
<div class="stat-icon">👥</div>
<div class="stat-value">${users.length}</div>
<div class="stat-label">总用户数</div>
</div>
<div class="stat-card">
<div class="stat-icon">🆕</div>
<div class="stat-value">${users.filter(u => {
const today = new Date().toISOString().slice(0, 10);
return u.created_at && u.created_at.startsWith(today);
}).length}</div>
<div class="stat-label">今日新增</div>
</div>
<div class="stat-card">
<div class="stat-icon">📧</div>
<div class="stat-value">${users.filter(u => u.email).length}</div>
<div class="stat-label">已绑定邮箱</div>
</div>
<div class="stat-card">
<div class="stat-icon">📱</div>
<div class="stat-value">${users.length}</div>
<div class="stat-label">已绑定手机</div>
</div>
</div>
<div class="data-table">
<table>
<thead>
<tr>
<th>ID</th>
<th>头像</th>
<th>用户名</th>
<th>手机号</th>
<th>邮箱</th>
<th>注册时间</th>
<th>最后登录</th>
<th>操作</th>
</tr>
</thead>
<tbody>
${users.length === 0 ? '<tr><td colspan="8" style="text-align: center; color: #999;">暂无用户</td></tr>' :
users.map(u => `
<tr>
<td>${u.id}</td>
<td style="font-size: 24px;">${u.avatar || '👤'}</td>
<td>${u.username}</td>
<td>${u.phone}</td>
<td>${u.email || '-'}</td>
<td>${formatDate(u.created_at)}</td>
<td>${u.last_login_at ? formatDate(u.last_login_at) : '从未登录'}</td>
<td>
<div class="action-btns">
<button class="action-btn edit" onclick="showEditUserModal(${u.id})">编辑</button>
<button class="action-btn" style="background: #f59e0b; color: white;" onclick="showResetPasswordModal(${u.id})">重置密码</button>
<button class="action-btn delete" onclick="deleteUser(${u.id})">删除</button>
</div>
</td>
</tr>
`).join('')
}
</tbody>
</table>
</div>
`;
}
function formatDate(dateStr) {
if (!dateStr) return '-';
const d = new Date(dateStr);
return d.toLocaleDateString() + ' ' + d.toLocaleTimeString().slice(0, 5);
}
async function searchUsers(event) {
if (event && event.key !== 'Enter') return;
const search = document.getElementById('userSearch').value.trim();
users = await fetchAPI(`/api/admin/users?search=${encodeURIComponent(search)}`);
loadUsersPage(document.getElementById('mainContent'));
}
async function searchUsersBtn() {
const search = document.getElementById('userSearch').value.trim();
users = await fetchAPI(`/api/admin/users?search=${encodeURIComponent(search)}`);
loadUsersPage(document.getElementById('mainContent'));
}
function showEditUserModal(id) {
const user = users.find(u => u.id === id);
if (!user) return;
const avatars = ['👤', '😊', '😎', '🤓', '🦸', '🧙', '🥷', '👨', '👩', '🧑', '👴', '👵', '👦', '👧', '🤖', '👽', '🧛', '🧜', '🧚', '🦊'];
showModal('编辑用户', `
<div class="form-group">
<label class="form-label">用户名</label>
<input type="text" class="form-input" id="editUserName" value="${user.username}">
</div>
<div class="form-group">
<label class="form-label">手机号(不可修改)</label>
<input type="text" class="form-input" id="editUserPhone" value="${user.phone}" readonly style="background: #f5f7fa;">
</div>
<div class="form-group">
<label class="form-label">邮箱</label>
<input type="email" class="form-input" id="editUserEmail" value="${user.email || ''}">
</div>
<div class="form-group">
<label class="form-label">头像</label>
<select class="form-select" id="editUserAvatar">
${avatars.map(a => `<option value="${a}" ${a === (user.avatar || '👤') ? 'selected' : ''}>${a}</option>`).join('')}
</select>
</div>
<div class="form-group">
<label class="form-label">个性签名</label>
<input type="text" class="form-input" id="editUserSignature" value="${user.signature || ''}" maxlength="50">
</div>
<div class="form-group">
<label class="form-label">性别</label>
<select class="form-select" id="editUserGender">
<option value="" ${!user.gender ? 'selected' : ''}>未设置</option>
<option value="male" ${user.gender === 'male' ? 'selected' : ''}>男</option>
<option value="female" ${user.gender === 'female' ? 'selected' : ''}>女</option>
<option value="other" ${user.gender === 'other' ? 'selected' : ''}>其他</option>
</select>
</div>
<div class="form-group">
<label class="form-label">年龄</label>
<input type="number" class="form-input" id="editUserAge" value="${user.age || ''}" min="1" max="150">
</div>
<div class="form-group">
<label class="form-label">地区</label>
<input type="text" class="form-input" id="editUserRegion" value="${user.region || ''}">
</div>
<button class="form-submit" onclick="updateUser(${id})">保存</button>
`);
}
async function updateUser(id) {
const data = {
username: document.getElementById('editUserName').value,
email: document.getElementById('editUserEmail').value,
avatar: document.getElementById('editUserAvatar').value,
signature: document.getElementById('editUserSignature').value,
gender: document.getElementById('editUserGender').value,
age: document.getElementById('editUserAge').value ? parseInt(document.getElementById('editUserAge').value) : null,
region: document.getElementById('editUserRegion').value
};
if (!data.username) {
showToast('用户名不能为空');
return;
}
await fetchAPI(`/api/admin/users/${id}`, 'PUT', data);
closeModal();
showToast('更新成功');
loadPage('users');
}
function showResetPasswordModal(id) {
const user = users.find(u => u.id === id);
if (!user) return;
showModal('重置密码', `
<div style="padding: 16px; background: #f5f7fa; border-radius: 8px; margin-bottom: 16px;">
<p><strong>用户:</strong> ${user.username}</p>
<p><strong>手机:</strong> ${user.phone}</p>
</div>
<div class="form-group">
<label class="form-label">新密码</label>
<input type="text" class="form-input" id="resetPassword" placeholder="输入新密码至少6位">
</div>
<p style="color: #999; font-size: 12px;">⚠️ 重置后用户需使用新密码登录</p>
<button class="form-submit" onclick="resetPassword(${id})">确认重置</button>
`);
}
async function resetPassword(id) {
const password = document.getElementById('resetPassword').value;
if (!password || password.length < 6) {
showToast('密码长度至少6位');
return;
}
await fetchAPI(`/api/admin/users/${id}/password`, 'PUT', { password });
closeModal();
showToast('密码已重置');
}
async function deleteUser(id) {
const user = users.find(u => u.id === id);
if (!user) return;
if (!confirm(`确定删除用户 "${user.username}"\n此操作不可恢复!`)) return;
await fetchAPI(`/api/admin/users/${id}`, 'DELETE');
showToast('删除成功');
loadPage('users');
}
// ==================== 大模型配置页面 ====================
async function loadLLMPage(content) {
@@ -512,39 +731,6 @@ function showAddAgentModal() {
<button class="form-submit" onclick="saveAgent()">保存</button>
`);
}
</select>
</div>
<div class="form-group">
<label class="form-label">描述</label>
<input type="text" class="form-input" id="agentDescription" placeholder="智能体描述">
</div>
<div class="form-group">
<label class="form-label">System Prompt</label>
<textarea class="form-textarea" id="agentPrompt" placeholder="系统提示词"></textarea>
</div>
<div class="form-group">
<label class="form-label">LLM配置</label>
<select class="form-select" id="agentLLM">
<option value="">使用默认</option>
${llmConfigs.map(c => `<option value="${c.id}">${c.name}</option>`).join('')}
</select>
</div>
<div class="form-group">
<label class="form-label">热度</label>
<input type="number" class="form-input" id="agentHeat" value="0">
</div>
<div class="form-group">
<label class="form-label">标签</label>
<input type="text" class="form-input" id="agentTags" placeholder="多个标签用逗号分隔">
</div>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="agentEnableSearch"> 启用联网搜索
</label>
</div>
<button class="form-submit" onclick="saveAgent()">保存</button>
`);
}
function showEditAgentModal(agentId) {
const agent = agents.find(a => a.agent_id === agentId);