vproxy / src /aistudio_api /static /login.html
jlshu's picture
Upload 85 files
95f8c4b verified
Raw History Blame Contribute Delete
5.39 kB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>登录 - AI Studio Proxy</title>
<link rel="stylesheet" href="/static/style.css">
<style>
body {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background: var(--bg-alt);
}
.login-container {
width: 100%;
max-width: 420px;
padding: 24px;
}
.login-card {
background: #fff;
border-radius: 16px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
padding: 40px 32px;
}
.login-header {
text-align: center;
margin-bottom: 32px;
}
.login-logo {
width: 56px;
height: 56px;
background: var(--green);
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 700;
font-size: 24px;
margin: 0 auto 16px;
}
.login-header h1 {
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
}
.login-header p {
font-size: 14px;
color: var(--muted);
}
.login-field {
margin-bottom: 20px;
}
.login-field label {
display: block;
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
}
.login-input {
width: 100%;
padding: 12px 14px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 15px;
font-family: inherit;
outline: none;
transition: border-color 0.15s, box-shadow 0.15s;
}
.login-input:focus {
border-color: var(--green);
box-shadow: 0 0 0 3px rgba(16, 163, 127, 0.12);
}
.login-input::placeholder {
color: #a3a3a3;
}
.login-btn {
width: 100%;
padding: 12px;
background: var(--green);
color: #fff;
border: none;
border-radius: 10px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s;
font-family: inherit;
}
.login-btn:hover {
background: #0e8c6b;
}
.login-btn:disabled {
background: var(--border);
cursor: not-allowed;
}
.login-error {
background: #fef2f2;
color: #991b1b;
padding: 12px 14px;
border-radius: 10px;
font-size: 14px;
margin-bottom: 20px;
display: none;
}
.login-error.show {
display: block;
}
.login-footer {
text-align: center;
margin-top: 24px;
font-size: 13px;
color: var(--muted);
}
.login-footer a {
color: var(--green);
text-decoration: none;
font-weight: 500;
}
.login-footer a:hover {
text-decoration: underline;
}
</style>
</head>
<body x-data="loginApp()" x-init="init()">
<div class="login-container">
<div class="login-card">
<div class="login-header">
<div class="login-logo">AI</div>
<h1>AI Studio Proxy</h1>
<p>请输入 API Token 登录系统</p>
</div>
<div class="login-error" :class="{show: error}" x-text="error"></div>
<form @submit.prevent="handleLogin()">
<div class="login-field">
<label for="token">API Token</label>
<input id="token" class="login-input" type="password" x-model="token" placeholder="输入您的 API Token" autofocus
autocomplete="current-password">
</div>
<button type="submit" class="login-btn" :disabled="loading || !token.trim()">
<span x-show="!loading">登录</span>
<span x-show="loading">验证中...</span>
</button>
</form>
<div class="login-footer">
<p>Token 由管理员提供,请联系管理员获取</p>
</div>
</div>
</div>
<script>
function loginApp() {
return {
token: '',
error: '',
loading: false,
init() {
// 检查是否已有 token
const savedToken = localStorage.getItem('asp_api_token');
if (savedToken) {
this.token = savedToken;
// 尝试自动登录
this.handleLogin();
}
},
async handleLogin() {
const token = this.token.trim();
if (!token) return;
this.error = '';
this.loading = true;
try {
// 调用 /stats 端点验证 token(该端点受 API Key 保护)
const res = await fetch('/stats', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (res.ok) {
// 登录成功,保存 token 并跳转
localStorage.setItem('asp_api_token', token);
window.location.href = '/';
} else if (res.status === 401) {
this.error = 'Token 无效,请检查后重试';
} else {
this.error = `验证失败 (${res.status}),请稍后重试`;
}
} catch (e) {
this.error = '网络错误,请检查连接后重试';
} finally {
this.loading = false;
}
}
}
}
</script>
<script defer src="/static/alpine.min.js"></script>
</body>
</html>