AIClient-2-API/static/login.html
hex2077 2d317e0333 refactor(项目结构): 重构项目目录结构并优化代码组织
将常用工具函数移动到utils目录
重构提供商策略模式实现
新增docker-compose构建配置文件
优化UI配置选择器的样式和交互
重构代理工具和API管理模块
更新脚本路径和依赖引用
2026-01-10 18:19:06 +08:00

349 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; 2025 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 {
showError(data.message || t('login.error.incorrect'));
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>