- 将导航菜单配置集中到常量文件便于统一管理 - 实现基于Pinia的状态管理替换本地存储 - 优化路由守卫逻辑增加错误处理 - 使用动态导入实现路由懒加载 - 统一侧边栏和主布局的菜单渲染逻辑
206 lines
9.3 KiB
Vue
206 lines
9.3 KiB
Vue
<template>
|
||
<div class="min-h-screen flex items-center justify-center bg-gray-900 text-white p-4">
|
||
<div class="w-full max-w-md">
|
||
<div class="text-center mb-8">
|
||
<div class="flex items-center justify-center space-x-2 mb-2">
|
||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-green-500 to-blue-600 flex items-center justify-center">
|
||
<span class="font-bold text-xl">A</span>
|
||
</div>
|
||
<span class="font-bold text-xl">AriStockAI</span>
|
||
</div>
|
||
<p class="text-gray-400">
|
||
没有账号?
|
||
<a href="#" class="text-green-400 hover:underline" @click.prevent="$router.push('/sign-up')">注册</a>
|
||
</p>
|
||
</div>
|
||
|
||
<div class="bg-gray-800 rounded-2xl p-8 shadow-lg">
|
||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||
<button @click="handleGoogleLogin" class="py-3 px-4 bg-white text-gray-900 rounded-lg font-medium flex items-center justify-center hover:bg-gray-100 transition-colors">
|
||
<svg class="w-5 h-5 mr-2" viewBox="0 0 24 24" fill="currentColor">
|
||
<path d="M12 22q-2.05 0-3.875-.788t-3.188-2.15-2.15-3.187T2 12q0-2.05.788-3.875t2.15-3.188 3.187-2.15T12 2q2.05 0 3.875.788t3.188 2.15 2.15 3.187T22 12q0 2.05-.788 3.875t-2.15 3.188-3.187 2.15T12 22zm0-2q3.333 0 5.667-2.333T20 12q0-3.333-2.333-5.667T12 4Q8.667 4 6.333 6.333T4 12q0 3.333 2.333 5.667T12 20zm0-8q1.657 0 2.828-.828T16 8q0-1.657-.828-2.828T12 4Q10.343 4 9.172 5.172T8 8q0 1.657.828 2.828T12 12zm-6 4h12q-.667-1.333-2.333-2T12 12t-3.667.667T6 14z"/>
|
||
</svg>
|
||
Google
|
||
</button>
|
||
<button @click="handleTwitterLogin" class="py-3 px-4 bg-white text-gray-900 rounded-lg font-medium flex items-center justify-center hover:bg-gray-100 transition-colors">
|
||
<svg class="w-5 h-5 mr-2" viewBox="0 0 24 24" fill="currentColor">
|
||
<path d="M22.46 6c-.77.35-1.6.58-2.46.69.88-.53 1.56-1.37 1.88-2.38-.83.5-1.75.85-2.72 1.05C18.37 4.5 17.26 4 16 4c-2.35 0-4.27 1.92-4.27 4.29 0 .34.04.67.11.98C8.28 9.09 4.11 7.38 1.67 4.79c-.4.69-.64 1.52-.64 2.46 0 1.71.87 3.21 2.18 4.09-.81-.02-1.56-.25-2.22-.64v.06c0 2.08 1.48 3.82 3.44 4.21a4.22 4.22 0 0 1-1.88.07 4.28 4.28 0 0 0 4 2.98 8.521 8.521 0 0 1-5.33 1.84c-.34 0-.68-.02-1.02-.06C1.88 20.29 4.49 21 7.34 21c8.45 0 13.17-6.98 13.17-13.17 0-.2-.01-.4-.02-.6.9-.63 1.65-1.43 2.25-2.34z"/>
|
||
</svg>
|
||
Twitter
|
||
</button>
|
||
<button @click="handleFacebookLogin" class="py-3 px-4 bg-white text-gray-900 rounded-lg font-medium flex items-center justify-center hover:bg-gray-100 transition-colors">
|
||
<svg class="w-5 h-5 mr-2" viewBox="0 0 24 24" fill="currentColor">
|
||
<path d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z"/>
|
||
</svg>
|
||
Facebook
|
||
</button>
|
||
<button @click="handleMicrosoftLogin" class="py-3 px-4 bg-white text-gray-900 rounded-lg font-medium flex items-center justify-center hover:bg-gray-100 transition-colors">
|
||
<svg class="w-5 h-5 mr-2" viewBox="0 0 24 24" fill="currentColor">
|
||
<path d="M12 2.25c-5.38 0-9.75 4.37-9.75 9.75s4.37 9.75 9.75 9.75 9.75-4.37 9.75-9.75S17.38 2.25 12 2.25zm3.75 11.62c0 2.9-2.35 5.25-5.25 5.25s-5.25-2.35-5.25-5.25 2.35-5.25 5.25-5.25h1.5c.83 0 1.5-.67 1.5-1.5s-.67-1.5-1.5-1.5-1.5.67-1.5 1.5H12c-3.86 0-7 3.14-7 7s3.14 7 7 7 7-3.14 7-7v-1.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5z"/>
|
||
</svg>
|
||
Microsoft
|
||
</button>
|
||
</div>
|
||
|
||
<div class="flex items-center justify-between my-6">
|
||
<div class="h-px bg-gray-700 flex-grow"></div>
|
||
<span class="px-4 text-gray-400">OR</span>
|
||
<div class="h-px bg-gray-700 flex-grow"></div>
|
||
</div>
|
||
|
||
<div class="space-y-4">
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-400 mb-1">邮箱</label>
|
||
<div class="relative">
|
||
<input type="email" v-model="email" placeholder="输入您的邮箱" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-green-500 text-white placeholder-gray-500"/>
|
||
<svg class="absolute right-3 top-3.5 w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/>
|
||
</svg>
|
||
</div>
|
||
<p v-if="emailError" class="text-red-400 text-xs mt-1">{{ emailError }}</p>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-400 mb-1">密码</label>
|
||
<div class="relative">
|
||
<input :type="showPassword ? 'text' : 'password'" v-model="password" placeholder="输入您的密码" class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-green-500 text-white placeholder-gray-500"/>
|
||
<button type="button" class="absolute right-3 top-3.5 text-gray-500" @click="showPassword = !showPassword">
|
||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
<p v-if="passwordError" class="text-red-400 text-xs mt-1">{{ passwordError }}</p>
|
||
</div>
|
||
<div class="text-right mb-6">
|
||
<a href="#" class="text-sm text-gray-400 hover:underline">Forgot Password</a>
|
||
</div>
|
||
|
||
<button class="w-full py-3 px-4 bg-gradient-to-r from-green-500 to-blue-600 rounded-lg font-medium hover:opacity-90 transition-opacity" @click="handleLogin()">
|
||
登录
|
||
</button>
|
||
|
||
<p class="text-xs text-gray-500 text-center mt-6">
|
||
By using AriStockAI, you agree to the <a href="#" class="text-gray-400 hover:underline">使用条款</a> and <a href="#" class="text-gray-400 hover:underline">隐私政策</a>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<el-dialog
|
||
v-model="showSuccessModal"
|
||
title="登录成功"
|
||
:closable="false"
|
||
:show-close="false"
|
||
:modal-append-to-body="false"
|
||
:append-to-body="true"
|
||
>
|
||
<div class="text-center py-4">
|
||
<div class="w-16 h-16 bg-green-500 rounded-full flex items-center justify-center mx-auto mb-4">
|
||
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||
</svg>
|
||
</div>
|
||
<p class="text-gray-400 mb-6">您已成功登录,即将跳转到主页</p>
|
||
</div>
|
||
<template #footer>
|
||
<div class="flex justify-center">
|
||
<el-button
|
||
type="primary"
|
||
@click="handleModalClose"
|
||
class="px-6 py-2 bg-gradient-to-r from-green-500 to-blue-600 rounded-lg"
|
||
>
|
||
确定
|
||
</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { useUserStore } from '@/store/userStore';
|
||
|
||
export default {
|
||
|
||
name: 'Login',
|
||
data() {
|
||
return {
|
||
email: '',
|
||
password: '',
|
||
showPassword: false,
|
||
emailError: '',
|
||
passwordError: '',
|
||
showSuccessModal: false
|
||
}
|
||
},
|
||
methods: {
|
||
validateForm() {
|
||
let isValid = true;
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
|
||
// 邮箱验证
|
||
if (!this.email) {
|
||
this.emailError = '请输入邮箱';
|
||
isValid = false;
|
||
} else if (!emailRegex.test(this.email)) {
|
||
this.emailError = '请输入有效的邮箱地址';
|
||
isValid = false;
|
||
} else {
|
||
this.emailError = '';
|
||
}
|
||
|
||
// 密码验证
|
||
if (!this.password) {
|
||
this.passwordError = '请输入密码';
|
||
isValid = false;
|
||
} else {
|
||
this.passwordError = '';
|
||
}
|
||
|
||
return isValid;
|
||
},
|
||
|
||
async handleLogin() {
|
||
if (!this.validateForm()) return;
|
||
|
||
const userStore = useUserStore();
|
||
try {
|
||
await userStore.login({ email: this.email, password: this.password });
|
||
this.showSuccessModal = true;
|
||
} catch (error) {
|
||
console.error('登录失败:', error);
|
||
this.passwordError = error.message || '登录失败,请检查您的邮箱和密码';
|
||
}
|
||
},
|
||
|
||
handleGoogleLogin() {
|
||
// 模拟第三方登录
|
||
localStorage.setItem('isLoggedIn', 'true');
|
||
this.showSuccessModal = true;
|
||
},
|
||
|
||
handleTwitterLogin() {
|
||
// 模拟第三方登录
|
||
localStorage.setItem('isLoggedIn', 'true');
|
||
this.showSuccessModal = true;
|
||
},
|
||
|
||
handleFacebookLogin() {
|
||
// 模拟第三方登录
|
||
localStorage.setItem('isLoggedIn', 'true');
|
||
this.showSuccessModal = true;
|
||
},
|
||
|
||
handleMicrosoftLogin() {
|
||
// 模拟第三方登录
|
||
localStorage.setItem('isLoggedIn', 'true');
|
||
this.showSuccessModal = true;
|
||
},
|
||
handleModalClose() {
|
||
this.showSuccessModal = false;
|
||
this.$router.push('/app');
|
||
}
|
||
}
|
||
}
|
||
</script> |