331 lines
10 KiB
HTML
331 lines
10 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>登录 - Tradus|AI 金融智能体</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">Tradus|AI金融智能体</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>
|