Files
note-manager/web/admin/login.html
T
Your Name 13c53fea0a feat: 管理员跨租户管理 + 注册开关
- 管理员可管理任意用户笔记(读取/修改/删除/回收站/标签/图谱/FTS 全平台)
- 普通用户仍数据隔离, 越权返回404
- 新增注册开关: 管理员后台⚙设置可开/关, 支持REGISTRATION_ENABLED环境变量
- 注册关闭时前台/登录页隐藏注册入口, 注册接口返回400
- 冒烟测试扩展到91用例全过
2026-08-11 13:07:56 +08:00

236 lines
8.7 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>后台管理登录 - 云笔记</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.login-box {
background: #fff;
padding: 40px;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
width: 100%;
max-width: 400px;
}
.login-box h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
font-size: 24px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #666;
font-size: 14px;
}
.form-group input {
width: 100%;
padding: 12px 16px;
border: 2px solid #e1e1e1;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s;
}
.form-group input:focus {
outline: none;
border-color: #667eea;
}
.btn-login {
width: 100%;
padding: 14px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: transform 0.2s;
}
.btn-login:hover {
transform: translateY(-2px);
}
.btn-login:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.error-msg {
color: #dc3545;
font-size: 14px;
margin-top: 10px;
text-align: center;
display: none;
}
.back-link {
display: block;
text-align: center;
margin-top: 20px;
color: #667eea;
text-decoration: none;
}
.switch-link {
display: block;
text-align: center;
margin-top: 12px;
color: #764ba2;
text-decoration: none;
font-size: 14px;
cursor: pointer;
}
.switch-link:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="login-box">
<h1 id="boxTitle">后台管理登录</h1>
<!-- 登录表单 -->
<form id="loginForm">
<div class="form-group">
<label>用户名</label>
<input type="text" id="username" placeholder="请输入用户名" autocomplete="username" required>
</div>
<div class="form-group">
<label>密码</label>
<input type="password" id="password" placeholder="请输入密码" autocomplete="current-password" required>
</div>
<button type="submit" class="btn-login" id="loginBtn">登 录</button>
<p class="error-msg" id="errorMsg"></p>
</form>
<!-- 注册表单 -->
<form id="registerForm" style="display:none;">
<div class="form-group">
<label>用户名</label>
<input type="text" id="regUsername" placeholder="设置用户名" autocomplete="username" required>
</div>
<div class="form-group">
<label>昵称(可选)</label>
<input type="text" id="regDisplay" placeholder="显示昵称" autocomplete="nickname">
</div>
<div class="form-group">
<label>密码(至少 6 位)</label>
<input type="password" id="regPassword" placeholder="设置密码" autocomplete="new-password" required>
</div>
<button type="submit" class="btn-login" id="regBtn">注 册</button>
<p class="error-msg" id="regErrorMsg"></p>
</form>
<a href="javascript:void(0)" class="switch-link" id="switchLink">没有账号?去注册</a>
<a href="/" class="back-link">返回前台</a>
</div>
<script>
const loginForm = document.getElementById('loginForm');
const registerForm = document.getElementById('registerForm');
const username = document.getElementById('username');
const password = document.getElementById('password');
const regUsername = document.getElementById('regUsername');
const regDisplay = document.getElementById('regDisplay');
const regPassword = document.getElementById('regPassword');
const loginBtn = document.getElementById('loginBtn');
const regBtn = document.getElementById('regBtn');
const errorMsg = document.getElementById('errorMsg');
const regErrorMsg = document.getElementById('regErrorMsg');
const switchLink = document.getElementById('switchLink');
const boxTitle = document.getElementById('boxTitle');
let isRegister = false;
function submit(btn, text) {
btn.disabled = true;
btn.textContent = text;
}
function reset(btn, text) {
btn.disabled = false;
btn.textContent = text;
}
function setMode(reg) {
isRegister = reg;
loginForm.style.display = reg ? 'none' : 'block';
registerForm.style.display = reg ? 'block' : 'none';
boxTitle.textContent = reg ? '注册账号' : '后台管理登录';
switchLink.textContent = reg ? '已有账号?去登录' : '没有账号?去注册';
errorMsg.style.display = 'none';
regErrorMsg.style.display = 'none';
}
switchLink.addEventListener('click', () => setMode(!isRegister));
// 若注册已关闭,隐藏「去注册」链接(从 /api/auth/me 读取)
(async () => {
try {
const res = await fetch('/api/auth/me');
const data = await res.json();
if (data.data && data.data.registration_enabled === false) {
switchLink.style.display = 'none';
}
} catch (e) { /* 保留默认 */ }
})();
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
errorMsg.style.display = 'none';
submit(loginBtn, '登录中...');
try {
const formData = new FormData();
formData.append('username', username.value);
formData.append('password', password.value);
const res = await fetch('/admin/login', { method: 'POST', body: formData });
const data = await res.json();
if (data.code === 0) {
window.location.href = '/admin/';
} else {
errorMsg.textContent = data.message;
errorMsg.style.display = 'block';
reset(loginBtn, '登 录');
}
} catch (err) {
errorMsg.textContent = '登录失败,请重试';
errorMsg.style.display = 'block';
reset(loginBtn, '登 录');
}
});
registerForm.addEventListener('submit', async (e) => {
e.preventDefault();
regErrorMsg.style.display = 'none';
submit(regBtn, '注册中...');
try {
const body = {
username: regUsername.value,
password: regPassword.value,
display_name: regDisplay.value
};
const res = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const data = await res.json();
if (data.code === 0) {
window.location.href = '/admin/';
} else {
regErrorMsg.textContent = data.message;
regErrorMsg.style.display = 'block';
reset(regBtn, '注 册');
}
} catch (err) {
regErrorMsg.textContent = '注册失败,请重试';
regErrorMsg.style.display = 'block';
reset(regBtn, '注 册');
}
});
</script>
</body>
</html>