2025-08-19 01:50:27 +08:00

347 lines
8.5 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="login-page">
<div class="login-container">
<div class="login-form">
<div class="form-header">
<h1>登录</h1>
<p>欢迎回到在线学习平台</p>
</div>
<n-form
ref="formRef"
:model="formData"
:rules="rules"
size="large"
@submit.prevent="handleSubmit"
>
<n-form-item path="email" label="邮箱">
<n-input
v-model:value="formData.email"
placeholder="请输入邮箱地址"
type="email"
>
<template #prefix>
<n-icon>
<MailOutline />
</n-icon>
</template>
</n-input>
</n-form-item>
<n-form-item path="password" label="密码">
<n-input
v-model:value="formData.password"
placeholder="请输入密码"
type="password"
show-password-on="mousedown"
>
<template #prefix>
<n-icon>
<LockClosedOutline />
</n-icon>
</template>
</n-input>
</n-form-item>
<n-form-item>
<div class="form-options">
<n-checkbox v-model:checked="rememberMe">
记住我
</n-checkbox>
<n-button text type="primary">
忘记密码
</n-button>
</div>
</n-form-item>
<n-form-item>
<n-button
type="primary"
size="large"
block
:loading="userStore.isLoading"
attr-type="submit"
>
登录
</n-button>
</n-form-item>
</n-form>
<div class="form-footer">
<p>
还没有账号
<n-button text type="primary" @click="$router.push('/register')">
立即注册
</n-button>
</p>
</div>
<!-- 社交登录 -->
<div class="social-login">
<n-divider>或使用以下方式登录</n-divider>
<n-space justify="center">
<n-button circle size="large">
<n-icon size="20">
<LogoGithub />
</n-icon>
</n-button>
<n-button circle size="large">
<n-icon size="20">
<LogoGoogle />
</n-icon>
</n-button>
<n-button circle size="large">
<n-icon size="20">
<LogoWechat />
</n-icon>
</n-button>
</n-space>
</div>
</div>
<!-- 侧边图片 -->
<div class="login-image">
<PlaceholderImage
:width="600"
:height="800"
text="登录背景图"
icon="🎨"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { useMessage, type FormInst, type FormRules } from 'naive-ui'
import { useUserStore } from '@/stores/user'
import PlaceholderImage from '@/components/common/PlaceholderImage.vue'
import {
MailOutline,
LockClosedOutline,
LogoGithub,
LogoGoogle,
LogoWechat
} from '@vicons/ionicons5'
import { AuthApi } from '@/api'
const router = useRouter()
const message = useMessage()
const userStore = useUserStore()
const formRef = ref<FormInst | null>(null)
const rememberMe = ref(false)
// 表单数据
const formData = reactive({
email: '',
password: ''
})
// 表单验证规则
const rules: FormRules = {
email: [
{
required: true,
message: '请输入邮箱地址',
trigger: ['input', 'blur']
},
{
type: 'email',
message: '请输入有效的邮箱地址',
trigger: ['input', 'blur']
}
],
password: [
{
required: true,
message: '请输入密码',
trigger: ['input', 'blur']
},
{
min: 3,
message: '密码长度不能少于3位',
trigger: ['input', 'blur']
}
]
}
// 处理表单提交
const handleSubmit = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
// 显示加载状态
userStore.isLoading = true
// 调用登录API
const response = await AuthApi.login({
email: formData.email,
password: formData.password
})
if (response.code === 200) {
const { user, token, refreshToken } = response.data
// 保存token到store和本地存储
userStore.token = token
localStorage.setItem('X-Access-Token', token)
localStorage.setItem('token', token)
localStorage.setItem('refreshToken', refreshToken)
// 如果选择了记住我,设置更长的过期时间
if (rememberMe.value) {
localStorage.setItem('rememberMe', 'true')
}
try {
// 登录成功后立即调用用户信息接口获取完整的用户信息
console.log('🔍 登录成功,正在获取用户信息...')
const userInfoResponse = await AuthApi.getUserInfo()
if (userInfoResponse.success && userInfoResponse.result) {
// 将后端用户信息转换为前端格式
const convertedUser = AuthApi.convertBackendUserToUser(userInfoResponse.result)
console.log('🔍 转换后的用户信息:', convertedUser)
console.log('🔍 用户真实姓名:', convertedUser.profile?.realName)
console.log('🔍 用户头像:', convertedUser.avatar)
// 保存转换后的用户信息
userStore.user = convertedUser
localStorage.setItem('user', JSON.stringify(convertedUser))
console.log('✅ 用户信息获取成功并保存到store:', userStore.user)
} else {
// 如果获取用户信息失败,使用登录接口返回的基本用户信息
console.warn('⚠️ 获取用户信息失败,使用登录返回的基本信息')
userStore.user = user
localStorage.setItem('user', JSON.stringify(user))
}
} catch (userInfoError) {
// 如果获取用户信息失败,使用登录接口返回的基本用户信息
console.warn('⚠️ 获取用户信息异常,使用登录返回的基本信息:', userInfoError)
userStore.user = user
localStorage.setItem('user', JSON.stringify(user))
}
message.success('登录成功!')
// 登录成功后跳转到首页或之前的页面
const redirect = router.currentRoute.value.query.redirect as string
router.push(redirect || '/')
} else {
message.error(response.message || '登录失败')
}
} catch (error: any) {
console.error('登录失败:', error)
// 处理不同类型的错误
if (error.response?.status === 401) {
message.error('邮箱或密码错误')
} else if (error.response?.status === 429) {
message.error('登录尝试过于频繁,请稍后再试')
} else if (error.response?.data?.message) {
message.error(error.response.data.message)
} else {
message.error('网络错误,请检查网络连接')
}
} finally {
userStore.isLoading = false
}
}
</script>
<style scoped>
.login-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
}
.login-container {
display: grid;
grid-template-columns: 1fr 1fr;
max-width: 1000px;
width: 100%;
background: white;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.login-form {
padding: 60px 40px;
display: flex;
flex-direction: column;
justify-content: center;
}
.form-header {
text-align: center;
margin-bottom: 40px;
}
.form-header h1 {
font-size: 2rem;
color: #333;
margin-bottom: 8px;
}
.form-header p {
color: #666;
font-size: 1rem;
}
.form-options {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.form-footer {
text-align: center;
margin-top: 24px;
}
.social-login {
margin-top: 32px;
}
.login-image {
background: #f8f9fa;
display: flex;
align-items: center;
justify-content: center;
}
.login-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
@media (max-width: 768px) {
.login-container {
grid-template-columns: 1fr;
max-width: 400px;
}
.login-image {
display: none;
}
.login-form {
padding: 40px 24px;
}
}
</style>