Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e9357577cb |
286
backend/app.py
286
backend/app.py
@@ -147,6 +147,25 @@ def init_db():
|
||||
)
|
||||
''')
|
||||
|
||||
# 用户表(前端注册用户)
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
phone TEXT NOT NULL UNIQUE,
|
||||
email TEXT,
|
||||
avatar TEXT DEFAULT '👤',
|
||||
signature TEXT,
|
||||
gender TEXT,
|
||||
age INTEGER,
|
||||
region TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login_at TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# 初始化默认大模型配置
|
||||
cursor.execute('SELECT COUNT(*) FROM llm_configs')
|
||||
if cursor.fetchone()[0] == 0:
|
||||
@@ -295,6 +314,273 @@ def admin_login():
|
||||
return jsonify({'error': '用户名或密码错误'}), 401
|
||||
|
||||
|
||||
# ==================== 用户管理 ====================
|
||||
|
||||
@app.route('/api/register', methods=['POST'])
|
||||
def user_register():
|
||||
"""用户注册"""
|
||||
data = request.json
|
||||
username = data.get('username')
|
||||
password = data.get('password')
|
||||
phone = data.get('phone')
|
||||
email = data.get('email')
|
||||
code = data.get('code') # 验证码(暂时只校验格式)
|
||||
|
||||
# 参数验证
|
||||
if not username or not password or not phone:
|
||||
return jsonify({'error': '请填写完整信息'}), 400
|
||||
|
||||
# 用户名长度检查
|
||||
if len(username) < 2 or len(username) > 20:
|
||||
return jsonify({'error': '用户名长度应为2-20字符'}), 400
|
||||
|
||||
# 手机号格式检查
|
||||
import re
|
||||
if not re.match(r'^1[3-9]\d{9}$', phone):
|
||||
return jsonify({'error': '手机号格式不正确'}), 400
|
||||
|
||||
# 验证码检查(模拟,6位数字)
|
||||
if not code or not re.match(r'^\d{6}$', code):
|
||||
return jsonify({'error': '验证码格式不正确'}), 400
|
||||
|
||||
# 邮箱格式检查(可选)
|
||||
if email and not re.match(r'^[^\s@]+@[^\s@]+\.[^\s@]+$', email):
|
||||
return jsonify({'error': '邮箱格式不正确'}), 400
|
||||
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 检查用户名是否已存在
|
||||
cursor.execute('SELECT id FROM users WHERE username = ?', (username,))
|
||||
if cursor.fetchone():
|
||||
conn.close()
|
||||
return jsonify({'error': '用户名已存在'}), 400
|
||||
|
||||
# 检查手机号是否已存在
|
||||
cursor.execute('SELECT id FROM users WHERE phone = ?', (phone,))
|
||||
if cursor.fetchone():
|
||||
conn.close()
|
||||
return jsonify({'error': '手机号已注册'}), 400
|
||||
|
||||
# 创建用户
|
||||
password_hash = hashlib.sha256(password.encode()).hexdigest()
|
||||
cursor.execute('''
|
||||
INSERT INTO users (username, password_hash, phone, email)
|
||||
VALUES (?, ?, ?, ?)
|
||||
''', (username, password_hash, phone, email))
|
||||
|
||||
# 记录注册统计
|
||||
cursor.execute('INSERT INTO stats_logs (log_type, log_key) VALUES (?, ?)', ('user_register', 'new'))
|
||||
|
||||
conn.commit()
|
||||
user_id = cursor.lastrowid
|
||||
conn.close()
|
||||
|
||||
return jsonify({'success': True, 'user_id': user_id})
|
||||
|
||||
|
||||
@app.route('/api/login', methods=['POST'])
|
||||
def user_login():
|
||||
"""用户登录"""
|
||||
data = request.json
|
||||
username = data.get('username')
|
||||
password = data.get('password')
|
||||
|
||||
if not username or not password:
|
||||
return jsonify({'error': '请输入用户名和密码'}), 400
|
||||
|
||||
password_hash = hashlib.sha256(password.encode()).hexdigest()
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT * FROM users WHERE username = ? AND password_hash = ?', (username, password_hash))
|
||||
user = cursor.fetchone()
|
||||
|
||||
if user:
|
||||
# 更新最后登录时间
|
||||
cursor.execute('UPDATE users SET last_login_at = CURRENT_TIMESTAMP WHERE id = ?', (user['id'],))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'user': {
|
||||
'id': user['id'],
|
||||
'username': user['username'],
|
||||
'phone': user['phone'],
|
||||
'email': user['email'],
|
||||
'avatar': user['avatar'],
|
||||
'signature': user['signature'],
|
||||
'gender': user['gender'],
|
||||
'age': user['age'],
|
||||
'region': user['region']
|
||||
}
|
||||
})
|
||||
else:
|
||||
conn.close()
|
||||
return jsonify({'error': '用户名或密码错误'}), 401
|
||||
|
||||
|
||||
@app.route('/api/admin/users', methods=['GET'])
|
||||
def get_users():
|
||||
"""获取用户列表"""
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 支持搜索
|
||||
search = request.args.get('search', '')
|
||||
if search:
|
||||
cursor.execute('''
|
||||
SELECT id, username, phone, email, avatar, signature, gender, age, region,
|
||||
created_at, last_login_at FROM users
|
||||
WHERE username LIKE ? OR phone LIKE ? OR email LIKE ?
|
||||
ORDER BY created_at DESC
|
||||
''', (f'%{search}%', f'%{search}%', f'%{search}%'))
|
||||
else:
|
||||
cursor.execute('''
|
||||
SELECT id, username, phone, email, avatar, signature, gender, age, region,
|
||||
created_at, last_login_at FROM users ORDER BY created_at DESC
|
||||
''')
|
||||
|
||||
users = [dict(row) for row in cursor.fetchall()]
|
||||
conn.close()
|
||||
return jsonify(users)
|
||||
|
||||
|
||||
@app.route('/api/admin/users/<int:id>', methods=['GET'])
|
||||
def get_user(id):
|
||||
"""获取单个用户"""
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT * FROM users WHERE id = ?', (id,))
|
||||
user = cursor.fetchone()
|
||||
conn.close()
|
||||
|
||||
if user:
|
||||
return jsonify(dict(user))
|
||||
else:
|
||||
return jsonify({'error': '用户不存在'}), 404
|
||||
|
||||
|
||||
@app.route('/api/admin/users/<int:id>', methods=['PUT'])
|
||||
def update_user(id):
|
||||
"""更新用户信息"""
|
||||
data = request.json
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 检查用户是否存在
|
||||
cursor.execute('SELECT id FROM users WHERE id = ?', (id,))
|
||||
if not cursor.fetchone():
|
||||
conn.close()
|
||||
return jsonify({'error': '用户不存在'}), 404
|
||||
|
||||
# 检查用户名是否重复(如果要修改用户名)
|
||||
if data.get('username'):
|
||||
cursor.execute('SELECT id FROM users WHERE username = ? AND id != ?', (data['username'], id))
|
||||
if cursor.fetchone():
|
||||
conn.close()
|
||||
return jsonify({'error': '用户名已存在'}), 400
|
||||
|
||||
# 更新用户信息
|
||||
cursor.execute('''
|
||||
UPDATE users SET username=?, email=?, avatar=?, signature=?, gender=?, age=?, region=?,
|
||||
updated_at=CURRENT_TIMESTAMP WHERE id=?
|
||||
''', (data.get('username'), data.get('email'), data.get('avatar', '👤'),
|
||||
data.get('signature'), data.get('gender'), data.get('age'),
|
||||
data.get('region'), id))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
|
||||
|
||||
@app.route('/api/admin/users/<int:id>', methods=['DELETE'])
|
||||
def delete_user(id):
|
||||
"""删除用户"""
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('DELETE FROM users WHERE id = ?', (id,))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
|
||||
|
||||
@app.route('/api/admin/users/<int:id>/password', methods=['PUT'])
|
||||
def reset_user_password(id):
|
||||
"""重置用户密码"""
|
||||
data = request.json
|
||||
new_password = data.get('password')
|
||||
|
||||
if not new_password or len(new_password) < 6:
|
||||
return jsonify({'error': '密码长度至少6位'}), 400
|
||||
|
||||
password_hash = hashlib.sha256(new_password.encode()).hexdigest()
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('UPDATE users SET password_hash=?, updated_at=CURRENT_TIMESTAMP WHERE id=?',
|
||||
(password_hash, id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
|
||||
|
||||
@app.route('/api/user/<int:id>', methods=['PUT'])
|
||||
def update_user_profile(id):
|
||||
"""用户更新自己的资料"""
|
||||
data = request.json
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 检查用户是否存在
|
||||
cursor.execute('SELECT id FROM users WHERE id = ?', (id,))
|
||||
if not cursor.fetchone():
|
||||
conn.close()
|
||||
return jsonify({'error': '用户不存在'}), 404
|
||||
|
||||
# 更新用户资料
|
||||
cursor.execute('''
|
||||
UPDATE users SET avatar=?, signature=?, gender=?, age=?, region=?, email=?,
|
||||
updated_at=CURRENT_TIMESTAMP WHERE id=?
|
||||
''', (data.get('avatar', '👤'), data.get('signature'), data.get('gender'),
|
||||
data.get('age'), data.get('region'), data.get('email'), id))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
|
||||
|
||||
@app.route('/api/user/<int:id>/password', methods=['PUT'])
|
||||
def change_user_password(id):
|
||||
"""用户修改自己的密码"""
|
||||
data = request.json
|
||||
old_password = data.get('old_password')
|
||||
new_password = data.get('new_password')
|
||||
|
||||
if not old_password or not new_password:
|
||||
return jsonify({'error': '请输入旧密码和新密码'}), 400
|
||||
|
||||
if len(new_password) < 6:
|
||||
return jsonify({'error': '新密码长度至少6位'}), 400
|
||||
|
||||
old_hash = hashlib.sha256(old_password.encode()).hexdigest()
|
||||
new_hash = hashlib.sha256(new_password.encode()).hexdigest()
|
||||
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# 验证旧密码
|
||||
cursor.execute('SELECT id FROM users WHERE id = ? AND password_hash = ?', (id, old_hash))
|
||||
if not cursor.fetchone():
|
||||
conn.close()
|
||||
return jsonify({'error': '旧密码不正确'}), 400
|
||||
|
||||
# 更新密码
|
||||
cursor.execute('UPDATE users SET password_hash=?, updated_at=CURRENT_TIMESTAMP WHERE id=?',
|
||||
(new_hash, id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'success': True})
|
||||
|
||||
|
||||
# ==================== 大模型接口管理 ====================
|
||||
|
||||
@app.route('/api/admin/llm', methods=['GET'])
|
||||
|
||||
@@ -435,6 +435,10 @@
|
||||
<span class="sidebar-icon">📊</span>
|
||||
<span>统计信息</span>
|
||||
</div>
|
||||
<div class="sidebar-item" data-page="users">
|
||||
<span class="sidebar-icon">👥</span>
|
||||
<span>用户管理</span>
|
||||
</div>
|
||||
<div class="sidebar-item" data-page="llm">
|
||||
<span class="sidebar-icon">🧠</span>
|
||||
<span>大模型配置</span>
|
||||
|
||||
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);
|
||||
|
||||
156
www/app.js
156
www/app.js
@@ -2447,18 +2447,34 @@ function handleLogin() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查本地存储的用户
|
||||
const users = JSON.parse(localStorage.getItem('registeredUsers') || '[]');
|
||||
const user = users.find(u => u.username === username && u.password === password);
|
||||
|
||||
if (user) {
|
||||
currentUser = { username: user.username, registeredAt: user.registeredAt };
|
||||
// 调用后台登录API
|
||||
fetch('/api/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
currentUser = {
|
||||
id: data.user.id,
|
||||
username: data.user.username,
|
||||
phone: data.user.phone,
|
||||
email: data.user.email || '',
|
||||
avatar: data.user.avatar || '👤',
|
||||
signature: data.user.signature || '',
|
||||
registeredAt: Date.now()
|
||||
};
|
||||
saveCurrentUser();
|
||||
showToast('登录成功');
|
||||
showMainPage();
|
||||
} else {
|
||||
showToast('用户名或密码错误');
|
||||
showToast(data.error || '用户名或密码错误');
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
showToast('网络错误,请重试');
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 注册页面 ====================
|
||||
@@ -2477,12 +2493,21 @@ function showRegisterPage() {
|
||||
<div class="auth-form">
|
||||
<div class="auth-input-group">
|
||||
<label>用户名</label>
|
||||
<input type="text" id="registerUsername" placeholder="请输入用户名(3-20字符)" autocomplete="username">
|
||||
<input type="text" id="registerUsername" placeholder="请输入用户名(2-20字符)" autocomplete="username">
|
||||
</div>
|
||||
<div class="auth-input-group">
|
||||
<label>手机号 <span class="required">*</span></label>
|
||||
<input type="tel" id="registerPhone" placeholder="请输入手机号" maxlength="11" autocomplete="tel">
|
||||
</div>
|
||||
<div class="auth-input-group" style="display: flex; gap: 8px;">
|
||||
<div style="flex: 1;">
|
||||
<label>验证码 <span class="required">*</span></label>
|
||||
<input type="text" id="registerCode" placeholder="请输入验证码" maxlength="6">
|
||||
</div>
|
||||
<div style="flex-shrink: 0; align-self: flex-end;">
|
||||
<button class="send-code-btn" id="getCodeBtn">获取验证码</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="auth-input-group">
|
||||
<label>邮箱 <span class="optional">(可选)</span></label>
|
||||
<input type="email" id="registerEmail" placeholder="请输入邮箱(可选)" autocomplete="email">
|
||||
@@ -2527,6 +2552,12 @@ function showRegisterPage() {
|
||||
});
|
||||
}
|
||||
|
||||
// 获取验证码按钮
|
||||
const getCodeBtn = document.getElementById('getCodeBtn');
|
||||
if (getCodeBtn) {
|
||||
getCodeBtn.addEventListener('click', handleGetCode);
|
||||
}
|
||||
|
||||
// 回车注册
|
||||
document.getElementById('registerPasswordConfirm')?.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') handleRegister();
|
||||
@@ -2539,6 +2570,50 @@ function showRegisterPage() {
|
||||
e.target.value = e.target.value.replace(/\D/g, '');
|
||||
});
|
||||
}
|
||||
|
||||
// 验证码输入限制(只允许数字)
|
||||
const codeInput = document.getElementById('registerCode');
|
||||
if (codeInput) {
|
||||
codeInput.addEventListener('input', (e) => {
|
||||
e.target.value = e.target.value.replace(/\D/g, '');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 获取验证码(模拟)
|
||||
let verifyCode = '';
|
||||
let codeExpireTime = 0;
|
||||
|
||||
function handleGetCode() {
|
||||
const phone = document.getElementById('registerPhone')?.value.trim();
|
||||
|
||||
if (!validatePhone(phone)) {
|
||||
showToast('请先输入正确的手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成6位随机验证码
|
||||
verifyCode = Math.floor(100000 + Math.random() * 900000).toString();
|
||||
codeExpireTime = Date.now() + 5 * 60 * 1000; // 5分钟有效
|
||||
|
||||
// 模拟发送(实际项目中应调用短信API)
|
||||
showToast(`验证码已发送: ${verifyCode}(演示)`);
|
||||
|
||||
// 60秒倒计时
|
||||
const getCodeBtn = document.getElementById('getCodeBtn');
|
||||
if (getCodeBtn) {
|
||||
getCodeBtn.disabled = true;
|
||||
let countdown = 60;
|
||||
const timer = setInterval(() => {
|
||||
getCodeBtn.textContent = `${countdown}s`;
|
||||
countdown--;
|
||||
if (countdown <= 0) {
|
||||
clearInterval(timer);
|
||||
getCodeBtn.disabled = false;
|
||||
getCodeBtn.textContent = '获取验证码';
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// 验证手机号格式
|
||||
@@ -2559,12 +2634,13 @@ function handleRegister() {
|
||||
const username = document.getElementById('registerUsername')?.value.trim();
|
||||
const phone = document.getElementById('registerPhone')?.value.trim();
|
||||
const email = document.getElementById('registerEmail')?.value.trim();
|
||||
const code = document.getElementById('registerCode')?.value.trim();
|
||||
const password = document.getElementById('registerPassword')?.value;
|
||||
const passwordConfirm = document.getElementById('registerPasswordConfirm')?.value;
|
||||
|
||||
// 验证用户名
|
||||
if (!username || username.length < 3 || username.length > 20) {
|
||||
showToast('用户名需要3-20个字符');
|
||||
if (!username || username.length < 2 || username.length > 20) {
|
||||
showToast('用户名需要2-20个字符');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2574,8 +2650,20 @@ function handleRegister() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证验证码
|
||||
if (!code || code.length !== 6) {
|
||||
showToast('请输入6位验证码');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查验证码是否正确(模拟)
|
||||
if (code !== verifyCode || Date.now() > codeExpireTime) {
|
||||
showToast('验证码不正确或已过期');
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证邮箱(可选)
|
||||
if (!validateEmail(email)) {
|
||||
if (email && !validateEmail(email)) {
|
||||
showToast('请输入正确的邮箱格式');
|
||||
return;
|
||||
}
|
||||
@@ -2591,37 +2679,39 @@ function handleRegister() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
const users = JSON.parse(localStorage.getItem('registeredUsers') || '[]');
|
||||
if (users.find(u => u.username === username)) {
|
||||
showToast('用户名已存在');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查手机号是否已注册
|
||||
if (users.find(u => u.phone === phone)) {
|
||||
showToast('该手机号已注册');
|
||||
return;
|
||||
}
|
||||
|
||||
// 注册新用户
|
||||
const newUser = {
|
||||
// 调用后台注册API
|
||||
fetch('/api/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
username,
|
||||
phone,
|
||||
email: email || '',
|
||||
password,
|
||||
code
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// 注册成功,自动登录
|
||||
currentUser = {
|
||||
id: data.user_id,
|
||||
username: username,
|
||||
phone: phone,
|
||||
email: email || '',
|
||||
registeredAt: Date.now()
|
||||
};
|
||||
|
||||
users.push(newUser);
|
||||
localStorage.setItem('registeredUsers', JSON.stringify(users));
|
||||
|
||||
// 自动登录
|
||||
currentUser = { username: newUser.username, phone: newUser.phone, registeredAt: newUser.registeredAt };
|
||||
saveCurrentUser();
|
||||
|
||||
showToast('注册成功');
|
||||
showMainPage();
|
||||
} else {
|
||||
showToast(data.error || '注册失败');
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
showToast('网络错误,请重试');
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 限制提示 ====================
|
||||
|
||||
Reference in New Issue
Block a user