Spaces:
Paused
Paused
| <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> |