Files
happy-life-star/web/src/views/Login/index.vue
T

436 lines
10 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="container mx-auto px-4 py-8">
<div class="max-w-md mx-auto card">
<div class="text-center mb-8">
<h1 class="text-3xl font-bold text-gray-900 mb-2">登录</h1>
<p class="text-gray-600">欢迎回到情绪博物馆</p>
</div>
<el-form
ref="loginFormRef"
:model="loginForm"
:rules="loginRules"
label-width="80px"
@submit.prevent="handleLogin"
>
<el-form-item label="账号" prop="account">
<el-input
v-model="loginForm.account"
placeholder="请输入账号/邮箱/手机号"
:prefix-icon="User"
clearable
@keyup.enter="handleLogin"
/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="loginForm.password"
type="password"
placeholder="请输入密码"
:prefix-icon="Lock"
show-password
clearable
@keyup.enter="handleLogin"
/>
</el-form-item>
<el-form-item label="验证码" prop="captcha">
<div class="flex gap-2">
<el-input
v-model="loginForm.captcha"
placeholder="请输入验证码"
:prefix-icon="Key"
clearable
class="flex-1"
@keyup.enter="handleLogin"
/>
<div class="captcha-container" @click="refreshCaptcha">
<img
v-if="captchaImage"
:src="captchaImage"
alt="验证码"
class="captcha-image"
/>
<div v-else class="captcha-loading">
<el-icon class="is-loading"><Loading /></el-icon>
</div>
</div>
</div>
</el-form-item>
<el-form-item>
<div class="flex justify-between items-center mb-4">
<el-checkbox v-model="loginForm.rememberMe">
记住我
</el-checkbox>
<router-link to="/forgot-password" class="text-primary-600 hover:underline text-sm">
忘记密码
</router-link>
</div>
</el-form-item>
<el-form-item>
<el-button
type="primary"
class="w-full"
:loading="loading"
@click="handleLogin"
>
{{ loading ? '登录中...' : '登录' }}
</el-button>
</el-form-item>
</el-form>
<div class="divider">
<span></span>
</div>
<div class="social-login">
<el-button class="social-btn wechat" @click="handleSocialLogin('wechat')">
<el-icon><ChatDotRound /></el-icon>
微信登录
</el-button>
<el-button class="social-btn qq" @click="handleSocialLogin('qq')">
<el-icon><User /></el-icon>
QQ登录
</el-button>
</div>
<div class="text-center mt-6">
<router-link to="/register" class="text-primary-600 hover:underline">
还没有账号立即注册
</router-link>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage, ElForm } from 'element-plus'
import {
User,
Lock,
Key,
Loading,
ChatDotRound
} from '@element-plus/icons-vue'
import { useAuthStore } from '@/stores/auth'
import AuthService from '@/services/auth'
import { envConfig } from '@/config/env'
import type { LoginRequest } from '@/types/auth'
import type { FormInstance, FormRules } from 'element-plus'
const router = useRouter()
const route = useRoute()
const authStore = useAuthStore()
// 表单引用
const loginFormRef = ref<FormInstance>()
// 加载状态
const loading = ref(false)
// 验证码相关
const captchaImage = ref('')
const captchaKey = ref('')
// 登录表单数据
const loginForm = reactive<LoginRequest>({
account: '',
password: '',
captcha: '',
captchaKey: '',
rememberMe: false
})
// 表单验证规则
const loginRules: FormRules = {
account: [
{ required: true, message: '请输入账号', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度必须在6-20位之间', trigger: 'blur' }
],
captcha: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{ min: 4, max: 6, message: '验证码长度不正确', trigger: 'blur' }
]
}
/**
* 获取验证码
*/
const getCaptcha = async () => {
try {
const response = await AuthService.getCaptcha()
// 后端返回的数据已经包含了 data:image/png;base64, 前缀,直接使用
captchaImage.value = response.captchaImage
captchaKey.value = response.captchaKey
loginForm.captchaKey = response.captchaKey
} catch (error) {
console.error('获取验证码失败:', error)
ElMessage.error('获取验证码失败')
}
}
/**
* 刷新验证码
*/
const refreshCaptcha = () => {
loginForm.captcha = ''
getCaptcha()
}
/**
* 处理登录
*/
const handleLogin = async () => {
if (!loginFormRef.value) return
try {
console.log('开始登录流程...')
console.log('登录表单数据:', loginForm)
// 表单验证
await loginFormRef.value.validate()
console.log('表单验证通过')
loading.value = true
// 调用登录接口
console.log('调用登录接口...')
const success = await authStore.login(loginForm)
console.log('登录结果:', success)
if (success) {
// 登录成功,确保认证状态已正确设置
console.log('登录成功,当前认证状态:', {
isLoggedIn: authStore.isLoggedIn,
hasToken: !!authStore.accessToken,
hasUserInfo: !!authStore.userInfo
})
// 跳转到目标页面或首页
const redirect = route.query.redirect as string || '/'
console.log('登录成功,跳转到:', redirect)
// 使用路由跳转而不是window.location.href,避免base路径问题
await router.push(redirect)
} else {
// 登录失败,刷新验证码
console.log('登录失败,刷新验证码')
refreshCaptcha()
}
} catch (error) {
console.error('登录过程中发生错误:', error)
ElMessage.error('登录失败,请检查网络连接或稍后重试')
// 刷新验证码
refreshCaptcha()
} finally {
loading.value = false
}
}
/**
* 处理第三方登录
*/
const handleSocialLogin = (platform: 'wechat' | 'qq') => {
ElMessage.info(`${platform === 'wechat' ? '微信' : 'QQ'}登录功能开发中...`)
// TODO: 实现第三方登录逻辑
}
// 组件挂载时获取验证码
onMounted(() => {
getCaptcha()
// 如果已经登录,直接跳转
if (authStore.isLoggedIn) {
const redirect = route.query.redirect as string || '/'
router.push(redirect)
}
})
</script>
<style scoped>
.login-page {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-attachment: fixed;
display: flex;
align-items: center;
justify-content: center;
}
.container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
padding: 2rem;
width: 100%;
max-width: 450px;
}
.card {
background: transparent;
border: none;
box-shadow: none;
}
.captcha-container {
width: 120px;
height: 40px;
border: 1px solid #dcdfe6;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
background: #f5f7fa;
transition: border-color 0.3s;
}
.captcha-container:hover {
border-color: #409eff;
}
.captcha-image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 3px;
}
.captcha-loading {
color: #909399;
font-size: 14px;
}
.divider {
position: relative;
text-align: center;
margin: 1.5rem 0;
}
.divider::before {
content: '';
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 1px;
background: #e4e7ed;
}
.divider span {
background: rgba(255, 255, 255, 0.95);
padding: 0 1rem;
color: #909399;
font-size: 14px;
}
.social-login {
display: flex;
gap: 0.5rem;
}
.social-btn {
flex: 1;
border-radius: 8px;
font-weight: 500;
transition: all 0.3s;
}
.social-btn.wechat {
background: #07c160;
border-color: #07c160;
color: white;
}
.social-btn.wechat:hover {
background: #06ad56;
border-color: #06ad56;
transform: translateY(-1px);
}
.social-btn.qq {
background: #12b7f5;
border-color: #12b7f5;
color: white;
}
.social-btn.qq:hover {
background: #0ea5e9;
border-color: #0ea5e9;
transform: translateY(-1px);
}
:deep(.el-form-item__label) {
font-weight: 500;
color: #303133;
}
:deep(.el-input__wrapper) {
border-radius: 8px;
box-shadow: 0 0 0 1px #dcdfe6 inset;
transition: all 0.3s;
}
:deep(.el-input__wrapper:hover) {
box-shadow: 0 0 0 1px #c0c4cc inset;
}
:deep(.el-input__wrapper.is-focus) {
box-shadow: 0 0 0 1px #409eff inset;
}
:deep(.el-button--primary) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 8px;
font-weight: 500;
padding: 12px 20px;
transition: all 0.3s;
}
:deep(.el-button--primary:hover) {
background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.text-primary-600 {
color: #667eea;
text-decoration: none;
font-weight: 500;
transition: color 0.3s;
}
.text-primary-600:hover {
color: #5a6fd8;
text-decoration: underline;
}
/* 响应式设计 */
@media (max-width: 640px) {
.container {
margin: 1rem;
padding: 1.5rem;
border-radius: 16px;
}
.captcha-container {
width: 100px;
height: 36px;
}
.social-login {
flex-direction: column;
}
}
</style>