Files
grok2api/app/template/login.html
T
2025-10-10 16:59:56 +08:00

180 lines
7.7 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN" class="h-full">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - Grok2API</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
border: "hsl(0 0% 89%)",
input: "hsl(0 0% 89%)",
ring: "hsl(0 0% 3.9%)",
background: "hsl(0 0% 100%)",
foreground: "hsl(0 0% 3.9%)",
primary: {
DEFAULT: "hsl(0 0% 9%)",
foreground: "hsl(0 0% 98%)",
},
secondary: {
DEFAULT: "hsl(0 0% 96.1%)",
foreground: "hsl(0 0% 9%)",
},
muted: {
DEFAULT: "hsl(0 0% 96.1%)",
foreground: "hsl(0 0% 45.1%)",
},
destructive: {
DEFAULT: "hsl(0 84.2% 60.2%)",
foreground: "hsl(0 0% 98%)",
},
},
}
}
}
</script>
<style>
@keyframes slide-up {
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.animate-slide-up {
animation: slide-up 0.3s ease-out;
}
</style>
</head>
<body class="h-full bg-background text-foreground antialiased">
<div class="flex min-h-full flex-col justify-center py-12 px-4 sm:px-6 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<div class="text-center">
<h1 class="text-4xl font-bold">Grok2API</h1>
<p class="mt-2 text-sm text-muted-foreground">管理员控制台</p>
</div>
</div>
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<div class="bg-background py-8 px-4 sm:px-10 rounded-lg">
<form id="loginForm" class="space-y-6">
<!-- 账户 -->
<div class="space-y-2">
<label for="username" class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
账户
</label>
<input
type="text"
id="username"
name="username"
required
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 transition-colors"
placeholder="请输入用户名"
>
</div>
<!-- 密码 -->
<div class="space-y-2">
<label for="password" class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
密码
</label>
<input
type="password"
id="password"
name="password"
required
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 transition-colors"
placeholder="请输入密码"
>
</div>
<!-- 登录按钮 -->
<button
type="submit"
id="loginButton"
class="inline-flex items-center justify-center whitespace-nowrap rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 w-full text-sm"
>
登录
</button>
</form>
<div class="mt-6 text-center text-xs text-muted-foreground">
<p>Created By Chenyme © 2025</p>
</div>
</div>
</div>
</div>
<script>
const loginForm = document.getElementById('loginForm');
const loginButton = document.getElementById('loginButton');
loginForm.addEventListener('submit', async (e) => {
e.preventDefault();
loginButton.disabled = true;
loginButton.textContent = '登录中...';
try {
const formData = new FormData(loginForm);
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: formData.get('username'),
password: formData.get('password')
})
});
const data = await response.json();
if (data.success) {
localStorage.setItem('adminToken', data.token);
window.location.href = '/manage';
} else {
showToast(data.message || '登录失败', 'error');
}
} catch (error) {
showToast('网络错误,请稍后重试', 'error');
} finally {
loginButton.disabled = false;
loginButton.textContent = '登录';
}
});
function showToast(message, type = 'error') {
const toast = document.createElement('div');
const bgColors = {
success: 'bg-green-600',
error: 'bg-destructive',
info: 'bg-primary'
};
toast.className = `fixed bottom-4 right-4 ${bgColors[type] || bgColors.error} text-white px-4 py-2.5 rounded-lg shadow-lg text-sm font-medium z-50 animate-slide-up`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transition = 'opacity 0.3s';
setTimeout(() => toast.parentNode && document.body.removeChild(toast), 300);
}, 2000);
}
window.addEventListener('DOMContentLoaded', () => {
const token = localStorage.getItem('adminToken');
if (token) {
fetch('/api/stats', {
headers: { 'Authorization': `Bearer ${token}` }
}).then(response => {
if (response.ok) window.location.href = '/manage';
});
}
});
</script>
</body>
</html>