This commit is contained in:
2025-11-28 21:20:40 +08:00
commit f940b95b67
73 changed files with 15721 additions and 0 deletions

View File

@@ -0,0 +1,292 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>账号管理 - 管理后台</title>
<link rel="stylesheet" href="/static/css/ui-components.css?v=3">
<link rel="stylesheet" href="/static/css/admin.css">
</head>
<body class="admin-layout">
<header class="admin-header">
<div class="header-container">
<a href="/admin/dashboard" class="brand">
<span style="font-size: 1.5rem;"></span> JieXi Admin
</a>
<nav class="nav-links">
<a href="/admin/dashboard" class="nav-item">仪表板</a>
<a href="/admin/users" class="nav-item">用户管理</a>
<a href="/admin/apis" class="nav-item">接口管理</a>
<a href="/admin/config" class="nav-item">系统配置</a>
<a href="/admin/logs" class="nav-item">日志审计</a>
</nav>
<div class="user-actions">
<a href="/admin/profile" class="ui-btn ui-btn-secondary ui-btn-sm">账号设置</a>
<button class="ui-btn ui-btn-secondary ui-btn-sm" onclick="logout()">退出登录</button>
</div>
</div>
</header>
<main class="main-container">
<div class="page-header">
<h1 class="page-title">账号管理</h1>
</div>
<div class="stats-grid" style="grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));">
<!-- Basic Info Card -->
<div class="ui-card">
<h3 class="text-lg font-bold mb-4">基本信息</h3>
<div class="form-group">
<label>用户名</label>
<div class="text-lg font-medium" id="username">-</div>
</div>
<div class="form-group">
<label>邮箱</label>
<div class="text-lg" id="email">-</div>
</div>
<div class="form-group">
<label>2FA状态</label>
<div id="2faStatus">-</div>
</div>
<div class="form-group">
<label>最后登录</label>
<div class="text-sm text-muted">
<span id="lastLoginAt">-</span> (IP: <span id="lastLoginIp">-</span>)
</div>
</div>
</div>
<!-- Change Email Card -->
<div class="ui-card">
<h3 class="text-lg font-bold mb-4">修改邮箱</h3>
<form id="emailForm">
<div class="form-group">
<label>新邮箱地址</label>
<input type="email" id="newEmail" class="ui-input" required placeholder="admin@example.com">
</div>
<button type="submit" class="ui-btn ui-btn-primary w-full">保存邮箱</button>
</form>
</div>
<!-- Change Password Card -->
<div class="ui-card">
<h3 class="text-lg font-bold mb-4">修改密码</h3>
<form id="passwordForm">
<div class="form-group">
<label>原密码</label>
<input type="password" id="oldPassword" class="ui-input" required>
</div>
<div class="form-group">
<label>新密码</label>
<input type="password" id="newPassword" class="ui-input" required minlength="8">
<small class="text-muted text-sm">密码长度至少8位</small>
</div>
<div class="form-group">
<label>确认新密码</label>
<input type="password" id="confirmPassword" class="ui-input" required minlength="8">
</div>
<button type="submit" class="ui-btn ui-btn-primary w-full">修改密码</button>
</form>
</div>
<!-- 2FA Card -->
<div class="ui-card">
<h3 class="text-lg font-bold mb-4">两步验证 (2FA)</h3>
<p class="text-sm text-muted mb-4">启用2FA可以提高账号安全性需要使用Google Authenticator等应用扫描二维码。</p>
<div id="2faButtons">
<button class="ui-btn ui-btn-success w-full" onclick="enable2FA()">启用2FA</button>
</div>
<div id="2faSetup" style="display: none;">
<div style="text-align: center; margin-bottom: 1.5rem;">
<img id="qrCode" style="max-width: 200px; border-radius: 8px;">
</div>
<div class="form-group">
<label>验证码</label>
<input type="text" id="2faCode" class="ui-input" placeholder="请输入6位验证码" maxlength="6">
</div>
<div class="flex gap-2">
<button class="ui-btn ui-btn-success flex-1" onclick="verify2FA()">验证并启用</button>
<button class="ui-btn ui-btn-secondary flex-1" onclick="cancel2FA()">取消</button>
</div>
</div>
</div>
</div>
</main>
<script src="/static/js/ui-components.js"></script>
<script>
async function loadProfile() {
try {
const response = await fetch('/admin/api/profile');
const result = await response.json();
if (result.success) {
const data = result.data;
document.getElementById('username').textContent = data.username;
document.getElementById('email').textContent = data.email || '未设置';
document.getElementById('newEmail').value = data.email || '';
const is2faEnabled = data.is_2fa_enabled;
document.getElementById('2faStatus').innerHTML = is2faEnabled
? '<span class="badge badge-success">已启用</span>'
: '<span class="badge badge-neutral">未启用</span>';
document.getElementById('lastLoginIp').textContent = data.last_login_ip || '-';
document.getElementById('lastLoginAt').textContent = data.last_login_at
? new Date(data.last_login_at).toLocaleString('zh-CN')
: '-';
document.getElementById('2faButtons').innerHTML = is2faEnabled
? '<button class="ui-btn ui-btn-danger w-full" onclick="disable2FA()">禁用2FA</button>'
: '<button class="ui-btn ui-btn-success w-full" onclick="enable2FA()">启用2FA</button>';
}
} catch (error) {
UI.notify('加载失败: ' + error.message, 'error');
}
}
document.getElementById('emailForm').addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('newEmail').value;
try {
const response = await fetch('/admin/api/profile/email', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
});
const result = await response.json();
if (result.success) {
UI.notify('邮箱修改成功', 'success');
loadProfile();
} else {
UI.notify(result.message, 'error');
}
} catch (error) {
UI.notify('修改失败: ' + error.message, 'error');
}
});
document.getElementById('passwordForm').addEventListener('submit', async (e) => {
e.preventDefault();
const oldPassword = document.getElementById('oldPassword').value;
const newPassword = document.getElementById('newPassword').value;
const confirmPassword = document.getElementById('confirmPassword').value;
if (newPassword !== confirmPassword) {
UI.notify('两次输入的密码不一致', 'error');
return;
}
try {
const response = await fetch('/admin/api/profile/password', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ old_password: oldPassword, new_password: newPassword })
});
const result = await response.json();
if (result.success) {
UI.notify('密码修改成功', 'success');
document.getElementById('passwordForm').reset();
} else {
UI.notify(result.message, 'error');
}
} catch (error) {
UI.notify('修改失败: ' + error.message, 'error');
}
});
async function enable2FA() {
try {
const response = await fetch('/admin/api/2fa/enable', { method: 'POST' });
const result = await response.json();
if (result.success) {
document.getElementById('qrCode').src = result.qr_code;
document.getElementById('2faSetup').style.display = 'block';
document.getElementById('2faButtons').style.display = 'none';
} else {
UI.notify(result.message, 'error');
}
} catch (error) {
UI.notify('操作失败: ' + error.message, 'error');
}
}
async function verify2FA() {
const code = document.getElementById('2faCode').value;
if (!code || code.length !== 6) {
UI.notify('请输入6位验证码', 'error');
return;
}
try {
const response = await fetch('/admin/api/2fa/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
});
const result = await response.json();
if (result.success) {
UI.notify('2FA已启用', 'success');
cancel2FA();
loadProfile();
} else {
UI.notify(result.message, 'error');
}
} catch (error) {
UI.notify('操作失败: ' + error.message, 'error');
}
}
function cancel2FA() {
document.getElementById('2faSetup').style.display = 'none';
document.getElementById('2faButtons').style.display = 'block';
document.getElementById('2faCode').value = '';
}
async function disable2FA() {
UI.prompt('禁用2FA', '请输入2FA验证码', '', async (code) => {
if (!code) return;
try {
const response = await fetch('/admin/api/2fa/disable', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code })
});
const result = await response.json();
if (result.success) {
UI.notify('2FA已禁用', 'success');
loadProfile();
} else {
UI.notify(result.message, 'error');
}
} catch (error) {
UI.notify('操作失败: ' + error.message, 'error');
}
});
}
async function logout() {
try {
await fetch('/admin/logout', { method: 'POST' });
window.location.href = '/admin/login';
} catch (error) {
UI.notify('退出失败', 'error');
}
}
loadProfile();
</script>
</body>
</html>