AIClient-2-API/static/login.html
hex2077 05fea676b9 fix: 修复配置加载失败时回退到模拟数据的问题
移除模拟数据生成函数,当配置加载失败时显示空列表而非错误数据,提高用户体验
2026-03-05 17:53:21 +08:00

350 lines
No EOL
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN" id="html-root">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title data-i18n="login.title">登录 - AIClient2API</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.login-container {
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
padding: 40px;
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.logo {
text-align: center;
margin-bottom: 30px;
}
.logo img {
width: 80px;
height: 80px;
border-radius: 50%;
margin-bottom: 15px;
}
.logo h1 {
font-size: 24px;
color: #333;
margin-bottom: 5px;
}
.logo p {
font-size: 14px;
color: #666;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #333;
font-size: 14px;
font-weight: 500;
}
.form-group input {
width: 100%;
padding: 12px 15px;
border: 2px solid #e1e8ed;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
outline: none;
}
.form-group input:focus {
border-color: #059669;
box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.1);
}
.form-group input::placeholder {
color: #aaa;
}
.error-message {
color: #e74c3c;
font-size: 13px;
margin-top: 8px;
display: none;
animation: shake 0.3s ease-in-out;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-10px); }
75% { transform: translateX(10px); }
}
.error-message.show {
display: block;
}
.login-button {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #059669 0%, #10b981 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 10px;
}
.login-button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(5, 150, 105, 0.4);
}
.login-button:active {
transform: translateY(0);
}
.login-button:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
}
.footer {
text-align: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e1e8ed;
}
.footer p {
font-size: 13px;
color: #999;
}
.loading {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid #ffffff;
border-radius: 50%;
border-top-color: transparent;
animation: spin 0.8s linear infinite;
margin-right: 8px;
vertical-align: middle;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@media (max-width: 480px) {
.login-container {
padding: 30px 20px;
}
.logo h1 {
font-size: 20px;
}
}
</style>
<link rel="stylesheet" href="app/base.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<div style="position: absolute; top: 20px; right: 20px;" id="langSwitcherContainer"></div>
<div class="login-container">
<div class="logo">
<img src="/favicon.ico" alt="Logo" onerror="this.style.display='none'">
<h1>AIClient2API</h1>
<p data-i18n="login.heading">请登录以继续</p>
</div>
<form id="loginForm">
<div class="form-group">
<label for="password" data-i18n="login.password">密码</label>
<input
type="password"
id="password"
name="password"
data-i18n="login.passwordPlaceholder"
placeholder="请输入密码"
autocomplete="current-password"
required
>
<div class="error-message" id="errorMessage" data-i18n="login.error.incorrect">密码错误,请重试</div>
</div>
<button type="submit" class="login-button" id="loginButton" data-i18n="login.button">
登录
</button>
</form>
<div class="footer">
<p>&copy; 2077 AIClient2API. All rights reserved.</p>
</div>
</div>
<script type="module">
import { initI18n, t, setLanguage } from './app/i18n.js';
import { initLanguageSwitcher } from './app/language-switcher.js';
// 初始化多语言
initI18n();
// 初始化语言切换器
const langContainer = document.getElementById('langSwitcherContainer');
if (langContainer) {
import('./app/language-switcher.js').then(module => {
const switcher = module.createLanguageSwitcher();
langContainer.appendChild(switcher);
// 绑定事件逻辑(由于是动态创建,复用逻辑)
const languageBtn = switcher.querySelector('#languageBtn');
const languageDropdown = switcher.querySelector('#languageDropdown');
const languageOptions = switcher.querySelectorAll('.language-option');
languageBtn.addEventListener('click', (e) => {
e.stopPropagation();
languageDropdown.classList.toggle('show');
});
languageOptions.forEach(option => {
option.addEventListener('click', (e) => {
e.stopPropagation();
const lang = option.getAttribute('data-lang');
setLanguage(lang);
switcher.querySelector('.current-lang').textContent = lang === 'zh-CN' ? '中文' : 'EN';
languageOptions.forEach(opt => opt.classList.remove('active'));
option.classList.add('active');
languageDropdown.classList.remove('show');
});
});
document.addEventListener('click', () => {
languageDropdown.classList.remove('show');
});
});
}
const loginForm = document.getElementById('loginForm');
const passwordInput = document.getElementById('password');
const errorMessage = document.getElementById('errorMessage');
const loginButton = document.getElementById('loginButton');
// 检查是否已经登录
checkLoginStatus();
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
const password = passwordInput.value.trim();
if (!password) {
showError(t('login.error.empty'));
return;
}
// 禁用按钮并显示加载状态
loginButton.disabled = true;
loginButton.innerHTML = `<span class="loading"></span>${t('login.loggingIn')}`;
errorMessage.classList.remove('show');
try {
// 直接使用fetch进行登录请求登录页面不需要token
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
password
})
});
const data = await response.json();
if (response.ok && data.success) {
// 登录成功保存token
localStorage.setItem('authToken', data.token);
// 跳转到主页
window.location.href = '/';
} else {
const errorMsg = data.messageCode ? t(data.messageCode, data.messageParams) : (data.message || t('login.error.incorrect'));
showError(errorMsg);
loginButton.disabled = false;
loginButton.innerHTML = t('login.button');
passwordInput.value = '';
passwordInput.focus();
}
} catch (error) {
console.error('登录错误:', error);
showError(t('login.error.failed'));
loginButton.disabled = false;
loginButton.innerHTML = t('login.button');
}
});
function showError(message) {
errorMessage.textContent = message;
errorMessage.classList.add('show');
passwordInput.classList.add('error');
setTimeout(() => {
passwordInput.classList.remove('error');
}, 300);
}
function checkLoginStatus() {
const token = localStorage.getItem('authToken');
if (token) {
// Token存在跳转到主页
window.location.href = '/';
}
}
// 监听输入,清除错误提示
passwordInput.addEventListener('input', () => {
errorMessage.classList.remove('show');
});
// 页面加载时聚焦到密码输入框
passwordInput.focus();
</script>
</body>
</html>