feat: 后台管理添加用户管理功能 + 前端用户注册登录调用backend API
This commit is contained in:
252
www/admin.js
252
www/admin.js
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user