stock-ai-agent/frontend/login.html
2026-02-04 14:56:03 +08:00

331 lines
10 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">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - TradusAI 金融智能体</title>
<link rel="stylesheet" href="/static/css/style.css">
<style>
.login-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-primary);
padding: 20px;
}
.login-container {
width: 100%;
max-width: 400px;
padding: 40px;
background: var(--bg-secondary);
border: 1px solid var(--border-bright);
border-radius: 4px;
}
.login-header {
text-align: center;
margin-bottom: 40px;
}
.login-logo {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
margin-bottom: 16px;
}
.login-logo svg {
color: var(--accent);
}
.login-title {
font-size: 24px;
font-weight: 300;
color: var(--text-primary);
letter-spacing: 1px;
}
.login-form {
display: flex;
flex-direction: column;
gap: 20px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-label {
font-size: 13px;
color: var(--text-secondary);
letter-spacing: 0.5px;
}
.form-input {
width: 100%;
padding: 12px 16px;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 2px;
color: var(--text-primary);
font-size: 14px;
font-family: inherit;
transition: border-color 0.2s;
}
.form-input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 1px var(--accent);
}
.code-input-group {
display: flex;
gap: 12px;
}
.code-input-group .form-input {
flex: 1;
}
.send-code-btn {
padding: 12px 20px;
background: transparent;
border: 1px solid var(--border-bright);
border-radius: 2px;
color: var(--accent);
font-size: 13px;
cursor: pointer;
white-space: nowrap;
transition: all 0.2s;
}
.send-code-btn:hover:not(:disabled) {
background: var(--accent-dim);
border-color: var(--accent);
}
.send-code-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.login-btn {
width: 100%;
padding: 14px;
background: var(--accent);
border: none;
border-radius: 2px;
color: var(--bg-primary);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.login-btn:hover:not(:disabled) {
box-shadow: 0 0 16px var(--accent-dim);
}
.login-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.error-message {
padding: 12px;
background: rgba(255, 0, 64, 0.1);
border: 1px solid rgba(255, 0, 64, 0.3);
border-radius: 2px;
color: #ff0040;
font-size: 13px;
text-align: center;
}
.login-footer {
margin-top: 24px;
text-align: center;
font-size: 12px;
color: var(--text-tertiary);
}
</style>
</head>
<body>
<div id="app" class="login-page">
<div class="login-container">
<div class="login-header">
<div class="login-logo">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none">
<path d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z" fill="currentColor"/>
</svg>
</div>
<h1 class="login-title">TradusAI金融智能体</h1>
</div>
<form class="login-form" @submit.prevent="handleLogin">
<div class="form-group">
<label class="form-label">手机号</label>
<input
type="tel"
class="form-input"
v-model="phone"
placeholder="请输入手机号"
maxlength="11"
required
>
</div>
<div class="form-group">
<label class="form-label">验证码</label>
<div class="code-input-group">
<input
type="text"
class="form-input"
v-model="code"
placeholder="请输入验证码"
maxlength="6"
required
>
<button
type="button"
class="send-code-btn"
@click="sendCode"
:disabled="countdown > 0 || !isPhoneValid"
>
{{ countdown > 0 ? `${countdown}秒后重试` : '发送验证码' }}
</button>
</div>
</div>
<div v-if="errorMessage" class="error-message">
{{ errorMessage }}
</div>
<button
type="submit"
class="login-btn"
:disabled="loading || !isFormValid"
>
{{ loading ? '登录中...' : '登录' }}
</button>
</form>
<div class="login-footer">
登录即表示同意服务条款和隐私政策
</div>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
phone: '',
code: '',
countdown: 0,
loading: false,
errorMessage: ''
};
},
computed: {
isPhoneValid() {
return /^1[3-9]\d{9}$/.test(this.phone);
},
isFormValid() {
return this.isPhoneValid && this.code.length === 6;
}
},
methods: {
async sendCode() {
if (!this.isPhoneValid) {
this.errorMessage = '请输入正确的手机号';
return;
}
try {
const response = await fetch('/api/auth/send-code', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone: this.phone
})
});
const data = await response.json();
if (data.success) {
this.errorMessage = '';
this.countdown = 60;
this.startCountdown();
alert('验证码已发送');
} else {
this.errorMessage = data.message || '发送失败';
}
} catch (error) {
console.error('发送验证码失败:', error);
this.errorMessage = '发送失败,请稍后重试';
}
},
startCountdown() {
const timer = setInterval(() => {
this.countdown--;
if (this.countdown <= 0) {
clearInterval(timer);
}
}, 1000);
},
async handleLogin() {
if (!this.isFormValid) {
return;
}
this.loading = true;
this.errorMessage = '';
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
phone: this.phone,
code: this.code
})
});
const data = await response.json();
if (data.success && data.token) {
// 保存token
localStorage.setItem('token', data.token);
// 跳转到主页
window.location.href = '/app';
} else {
this.errorMessage = data.message || '登录失败';
}
} catch (error) {
console.error('登录失败:', error);
this.errorMessage = '登录失败,请稍后重试';
} finally {
this.loading = false;
}
}
}
}).mount('#app');
</script>
</body>
</html>