Compare commits

...

1 Commits

4 changed files with 646 additions and 80 deletions

View File

@@ -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'])

View File

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

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);

View File

@@ -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 };
saveCurrentUser();
showToast('登录成功');
showMainPage();
} else {
showToast('用户名或密码错误');
}
// 调用后台登录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(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 = {
username,
phone,
email: email || '',
password,
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();
// 调用后台注册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()
};
saveCurrentUser();
showToast('注册成功');
showMainPage();
} else {
showToast(data.error || '注册失败');
}
})
.catch(e => {
showToast('网络错误,请重试');
});
}
// ==================== 限制提示 ====================