AriStockAI/services/frontend/src/views/public/Login.vue
fanfpy fce2cf47f4 refactor(frontend): 重构前端导航菜单和权限系统
- 将导航菜单配置集中到常量文件便于统一管理
- 实现基于Pinia的状态管理替换本地存储
- 优化路由守卫逻辑增加错误处理
- 使用动态导入实现路由懒加载
- 统一侧边栏和主布局的菜单渲染逻辑
2025-07-18 19:09:25 +08:00

206 lines
9.3 KiB
Vue
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.

<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>