| | <!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> |
| | <link rel="icon" type="image/png" href="/static/favicon.png"> |
| | <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 .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">账户</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 placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50" |
| | placeholder="请输入账户"> |
| | </div> |
| | <div class="space-y-2"> |
| | <label for="password" class="text-sm font-medium">密码</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 placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50" |
| | placeholder="请输入密码"> |
| | </div> |
| | <button type="submit" id="loginButton" |
| | class="inline-flex items-center justify-center rounded-md font-medium transition-colors bg-primary text-primary-foreground hover:bg-primary/90 h-10 w-full disabled:opacity-50">登录</button> |
| | </form> |
| |
|
| | <div class="mt-6 text-center text-xs text-muted-foreground space-y-1"> |
| | <p>Created By Chenyme © 2025</p> |
| | <p>Fork 维护: @Tomiya233</p> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <script> |
| | const form = document.getElementById('loginForm'), btn = document.getElementById('loginButton'); |
| | form.addEventListener('submit', async (e) => { e.preventDefault(); btn.disabled = true; btn.textContent = '登录中...'; try { const fd = new FormData(form), r = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: fd.get('username'), password: fd.get('password') }) }); const d = await r.json(); d.success ? (localStorage.setItem('adminToken', d.token), location.href = '/manage') : showToast(d.message || '登录失败', 'error') } catch (e) { showToast('网络错误,请稍后重试', 'error') } finally { btn.disabled = false; btn.textContent = '登录' } }); |
| | function showToast(m, t = 'error') { const d = document.createElement('div'), bc = { success: 'bg-green-600', error: 'bg-destructive', info: 'bg-primary' }; d.className = `fixed bottom-4 right-4 ${bc[t] || bc.error} text-white px-4 py-2.5 rounded-lg shadow-lg text-sm font-medium z-50 animate-slide-up`; d.textContent = m; document.body.appendChild(d); setTimeout(() => { d.style.opacity = '0'; d.style.transition = 'opacity .3s'; setTimeout(() => d.parentNode && document.body.removeChild(d), 300) }, 2000) } |
| | window.addEventListener('DOMContentLoaded', () => { const t = localStorage.getItem('adminToken'); t && fetch('/api/stats', { headers: { Authorization: `Bearer ${t}` } }).then(r => { if (r.ok) location.href = '/manage' }) }); |
| | </script> |
| | </body> |
| |
|
| | </html> |