Files
AIGC/demo/frontend/src/views/Login.vue

710 lines
23 KiB
Vue
Raw Normal View History

2025-10-21 16:50:33 +08:00
<template>
<div class="login-page">
<!-- Logo -->
2025-11-13 17:01:39 +08:00
<div class="logo">
<img src="/images/backgrounds/logo.svg" alt="Logo" />
</div>
<!-- 登录卡片 -->
<div class="login-card">
<!-- 欢迎标题 -->
<div class="welcome-title">
<span class="welcome-text">欢迎来到</span>
<span class="brand-name">VidFlow</span>
2025-10-21 16:50:33 +08:00
</div>
2025-11-13 17:01:39 +08:00
<!-- 登录方式切换 -->
<div class="login-tabs">
<!-- 邮箱登录盒子 -->
<div
class="tab-item"
:class="{ active: loginType === 'email' }"
@click="loginType = 'email'"
>
<svg width="105" height="30" viewBox="0 0 105 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.598 6.112V25.638H11.076V24.13H4.316V25.638H1.768V6.112H6.344V2.55H8.996V6.112H13.598ZM4.316 21.712H6.344V16.122H4.316V21.712ZM8.996 21.712H11.076V16.122H8.996V21.712ZM4.316 13.73H6.344V8.556H4.316V13.73ZM8.996 8.556V13.73H11.076V8.556H8.996ZM15.34 3.772H24.232V5.748C23.452 8.4 22.62 10.818 21.736 13.054C23.556 15.654 24.466 17.76 24.492 19.398C24.492 20.958 24.154 22.024 23.504 22.596C22.802 23.22 21.398 23.532 19.318 23.532L18.512 20.802C19.474 20.906 20.28 20.984 20.956 20.984C21.372 20.958 21.658 20.828 21.814 20.62C21.918 20.464 21.97 20.048 21.996 19.398C21.97 17.786 20.982 15.68 19.032 13.054C19.864 11.156 20.67 8.868 21.45 6.164H17.914V26.73H15.34V3.772ZM27.716 12.326H31.616V9.83H34.346V12.326H37.57V14.926H34.346V15.446C35.542 16.538 36.79 17.734 38.038 19.06L36.53 21.348C35.698 20.048 34.97 18.93 34.346 18.046V26.756H31.616V18.202C30.654 20.23 29.432 22.102 27.924 23.818L26.754 20.776C28.756 19.06 30.238 17.11 31.226 14.926H27.716V12.326ZM49.4 10.48V26.73H46.774V25.82H41.106V26.73H38.48V10.48H49.4ZM41.106 23.428H46.774V21.244H41.106V23.428ZM41.106 18.956H46.774V17.058H41.106V18.956ZM41.106 14.77H46.774V12.872H41.106V14.77ZM31.538 6.762C30.966 7.75 30.316 8.634 29.614 9.466L27.248 7.958C28.782 6.294 29.874 4.5 30.498 2.576L33.124 3.148C32.968 3.564 32.838 3.98 32.682 4.37H38.974V6.762H35.568C36.088 7.49 36.504 8.192 36.842 8.842L34.346 9.778C33.878 8.738 33.306 7.724 32.682 6.762H31.538ZM42.64 6.762C42.12 7.828 41.574 8.816 40.95 9.726L38.636 8.244C39.962 6.424 40.898 4.474 41.444 2.446L44.018 3.018C43.862 3.486 43.732 3.928 43.602 4.37H50.83V6.762H46.67C47.19 7.49 47.632 8.192 47.97 8.842L45.578 9.752C45.11 8.712 44.538 7.724 43.862 6.762H42.64ZM57.538 13.522H72.566V20.282H57.538V13.522ZM69.836 17.89V15.888H60.268V17.89H69.836ZM60.45 20.438C61.282 21.296 62.036 22.31 62.712 23.454H67.626C68.354 22.466 68.978 21.426 69.524 20.36L72.046 21.27C71.578 22.05 71.084 22.778 70.564 23.454H76.232V26.028H53.716V23.454H59.826C59.28 22.726 58.63 22.05 57.902 21.4L60.45 20.438ZM56.368 6.112C57.564 6.996 58.63 7.854 59.514 8.686C60.424 7.802 61.152 6.866 61.724 5.878H55.822V3.382H64.792V5.41C64.194 6.918 63.388 8.27 62.374 9.466H68.822C67.262 7.75 66.014 5.904 65.104 3.876L67.366 2.628C67.782 3.59 68.276 4.5 68.848 5.358C69.758 4.63 70.538 3.85 71.188 3.018L73.086 4.708C72.306 5.644 71.37 6.528 70.304 7.308C70.72 7.828 71.188 8.296 71.708 8.764C72.8 7.932 73.71 6.996 74.49 5.982L76.388 7.646C75.608 8.634 74.672 9.544 73.632 10.35C74.776 11.182 76.024 11.962 77.428 12.664L75.634 14.744C73.606 13.6 71.838 12.352 70.33 10.974V11.936H60.788V11.104C59.124 12.56 57.07 13.782 54.626 14.796L52.962 12.664C54.782 11.962 56.316 11.156 57.616 10.246C56.758 9.466 55.744 8.66 54.548 7.828L56.368 6.112ZM82.368 3.408H97.864V11.806H102.258V14.276H98.228L100.282 15.966C98.93 17.63 97.396 18.956 95.68 19.892C97.604 21.296 99.84 22.518 102.388 23.61L101.01 26.002C97.37 24.286 94.432 22.232 92.196 19.814V23.974C92.196 25.794 91.39 26.73 89.804 26.73H86.762L86.164 24.182C87.1 24.286 88.01 24.364 88.894 24.364C89.284 24.364 89.492 24 89.492 23.324V19.918C86.944 22.154 83.928 24.156 80.47 25.95L79.378 23.428C83.278 21.66 86.632 19.58 89.492 17.136V14.276H79.768V11.806H95.108V10.012H83.252V7.672H95.108V5.852H82.368V3.408ZM83.018 14.666C84.526 15.706 85.8 16.746 86.84 17.786L85.072 19.554C84.162 18.566 82.888 17.526 81.224 16.382L83.018 14.666ZM98.176 14.276H92.196V16.824C92.69 17.37 93.236 17.89 93.834 18.41C95.498 17.422 96.954 16.044 98.176 14.276Z" fill="currentColor"/>
</svg>
</div>
<!-- 分隔线 -->
<div class="tab-divider"></div>
<!-- 账号登录盒子 -->
<div
class="tab-item"
:class="{ active: loginType === 'password' }"
@click="loginType = 'password'"
>
<svg width="105" height="30" viewBox="0 0 105 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.362 3.46V20.126H7.996V5.956H3.212V20.126H0.768V3.46H10.362ZM4.434 7.49H6.722V16.018C6.644 18.93 6.202 21.27 5.37 23.012C4.564 24.65 3.264 25.898 1.47 26.782L0.014 24.494C1.704 23.636 2.848 22.57 3.472 21.322C4.044 19.944 4.356 18.176 4.434 16.018V7.49ZM7.684 20.724C8.984 22.05 10.076 23.35 10.986 24.624L9.036 26.574C8.308 25.274 7.294 23.896 5.942 22.414L7.684 20.724ZM17.044 15.732H15.328V23.298C16.342 22.882 17.33 22.362 18.318 21.738L18.786 24.156C17.122 25.196 15.224 26.028 13.092 26.704L11.974 24.286C12.442 24.052 12.676 23.688 12.676 23.22V15.732H11.064V13.132H12.676V2.68H15.328V13.132H23.622V15.732H19.384C20.528 19.45 22.14 22.336 24.168 24.364L22.322 26.47C20.008 24.026 18.24 20.438 17.044 15.732ZM21.23 3.746L23.31 5.41C21.516 8.322 19.436 10.506 17.044 11.91L15.588 9.83C17.772 8.478 19.67 6.45 21.23 3.746ZM29.966 3.356H46.034V11.026H29.966V3.356ZM43.278 8.608V5.8H32.722V8.608H43.278ZM31.76 15.16H26.118V12.534H49.856V15.16H34.516L33.814 17.422H46.372C46.164 21.894 45.748 24.546 45.072 25.378C44.396 26.184 43.122 26.6 41.198 26.6C39.924 26.6 38.78 26.522 37.766 26.392L36.882 23.922C38.286 24.052 39.508 24.13 40.6 24.13C41.874 24.13 42.654 23.87 42.966 23.402C43.252 22.908 43.46 21.738 43.59 19.866H30.564L31.76 15.16ZM56.538 13.522H71.566V20.282H56.538V13.522ZM68.836 17.89V15.888H59.268V17.89H68.836ZM59.45 20.438C60.282 21.296 61.036 22.31 61.712 23.454H66.626C67.354 22.466 67.978 21.426 68.524 20.36L71.046 21.27C70.578 22.05 70.084 22.778 69.564 23.454H75.232V26.028H52.716V23.454H58.826C58.28 22.726 57.63 22.05 56.902 21.4L59.45 20.438ZM55.368 6.112C56.564 6.996 57.63 7.854 58.514 8.686C59.424 7.802 60.152 6.866 60.724 5.878H54.822V3.382H63.792V5.41C63.194 6.918 62.388 8.27 61.374 9.466H67.822C66.262 7.75 65.014 5.904 64.104 3.876L66.366 2.628C66.782 3.59 67.276 4.5 67.848 5.358C68.758 4.63 69.538 3.85 70.188 3.018L72.086 4.708C71.306 5.644 70.37 6.528 69.304 7.308C69.72 7.828 70.188 8.296 70.708 8.764C71.8 7.932 72.71 6.996 73.49 5.982L75.388 7.646C74.608 8.634 73.672 9.544 72.632 10.35C73.776 11.182 75.024 11.962 76.428 12.664L74.634 14.744C72.606 13.6 70.838 12.352 69.33 10.974V11.936H59.788V11.104C58.124 12.56 56.07 13.782 53.626 14.796L51.962 12.664C53.782 11.962 55.316 11.156 56.616 10.246C55.758 9.466 54.744 8.66 53.548 7.828L55.368 6.112ZM81.368 3.408H96.864V11.806H101.258V14.276H97.228L99.282 15.966C97.93 17.63 96.396 18.956 94.68 19.892C96.604 21.296 98.84 22.518 101.388 23.61L100.01 26.002C96.37 24.286 93.432 22.232 91.196 19.814V23.974C91.196 25.794 90.39 26.73 88.804 26.73H85.762L85.164 24.182C86.1 24.286 87.01 24.364 87.894 24.364C88.284 24.364 88.492 24 88.492 23.324V19.918C85.944 22.154 82.928 24.156 79.47 25.95L78.378 23.428C82.278 21.66 85.632 19.58 88.492 17.136V14.276H78.768V11.806H94.108V10.012H82.252V7.672H94.108V5.852H81.368V3.408ZM82.018 14.666C83.526 15.706 84.8 16.746 85.84 17.786L84.072 19.554C83.162 18.566 81.888 17.526 80.224 16.382L82.018 14.666ZM97.176 14.276H91.196V16.824C91.69 17.37 92.236 17.89 92.834 18.41C94.498 17.422 95.954 16.044 97.176 14.276Z" fill="currentColor"/>
</svg>
</div>
</div>
2025-10-21 16:50:33 +08:00
<!-- 登录表单 -->
<div class="login-form">
2025-11-13 17:01:39 +08:00
<!-- 邮箱登录 / 密码登录 表单 -->
<div class="email-login">
<!-- 邮箱输入 -->
<div class="email-input-group">
<el-input
2025-11-13 17:01:39 +08:00
ref="emailInput"
v-model="loginForm.email"
placeholder="请输入邮箱地址"
class="email-input"
type="email"
2025-11-13 17:01:39 +08:00
@keyup.enter="handleLogin"
/>
2025-11-13 17:01:39 +08:00
<div class="input-error" v-if="errors.email">{{ errors.email }}</div>
</div>
2025-11-13 17:01:39 +08:00
<!-- 验证码输入仅验证码登录显示 -->
<div class="code-input-wrapper" v-if="loginType === 'email'">
<el-input
2025-11-13 17:01:39 +08:00
ref="codeInput"
v-model="loginForm.code"
placeholder="请输入验证码"
class="code-input"
2025-11-13 17:01:39 +08:00
@keyup.enter="handleLogin"
@input="filterCodeSpaces"
>
<template #suffix>
<span
class="get-code-text"
:class="{ disabled: countdown > 0 || !isEmailValid }"
@click="getEmailCode"
>
{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
</span>
</template>
</el-input>
<div class="input-error" v-if="errors.code">{{ errors.code }}</div>
2025-10-21 16:50:33 +08:00
</div>
2025-11-13 17:01:39 +08:00
<!-- 密码输入仅密码登录显示 -->
<div v-if="loginType === 'password'" class="password-input-group">
<el-input ref="passwordInput" v-model="loginForm.password" placeholder="请输入密码" show-password @keyup.enter="handleLogin" />
<div class="input-error" v-if="errors.password">{{ errors.password }}</div>
</div>
2025-10-21 16:50:33 +08:00
</div>
<!-- 登录按钮 -->
<el-button
type="primary"
class="login-button"
:loading="userStore.loading"
@click="handleLogin"
>
{{ userStore.loading ? '登录中...' : '登录/注册' }}
2025-10-21 16:50:33 +08:00
</el-button>
<!-- 协议文字 -->
<p class="agreement-text">
登录即表示您同意遵守<router-link to="/user-agreement" class="agreement-link">用户协议</router-link><router-link to="/privacy-policy" class="agreement-link">隐私政策</router-link>
2025-10-21 16:50:33 +08:00
</p>
</div>
</div>
</div>
2025-10-21 16:50:33 +08:00
</template>
<script setup>
2025-11-13 17:01:39 +08:00
import { ref, reactive, onMounted, computed, nextTick } from 'vue'
2025-10-21 16:50:33 +08:00
import { useRouter, useRoute } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
import { loginWithEmail, login, sendEmailCode, setDevEmailCode, getCurrentUser } from '@/api/auth'
2025-10-21 16:50:33 +08:00
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
const countdown = ref(0)
let countdownTimer = null
2025-11-13 17:01:39 +08:00
const loginType = ref('email') // 'email' or 'password'
2025-10-21 16:50:33 +08:00
const loginForm = reactive({
email: '',
2025-11-13 17:01:39 +08:00
code: '',
password: ''
})
// inline errors for fields and server
const errors = reactive({
email: '',
code: '',
password: '',
server: ''
})
// input refs for focusing
const emailInput = ref(null)
const codeInput = ref(null)
const passwordInput = ref(null)
const isEmailValid = computed(() => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(loginForm.email))
const isCodeValid = computed(() => /^\d{6}$/.test(loginForm.code))
const isPasswordValid = computed(() => loginForm.password && loginForm.password.length >= 6)
const isFormValid = computed(() => {
if (loginType.value === 'email') {
return isEmailValid.value && isCodeValid.value
}
return isEmailValid.value && isPasswordValid.value
2025-10-21 16:50:33 +08:00
})
// 清空表单
2025-11-13 17:01:39 +08:00
const clearForm = async () => {
loginForm.email = ''
2025-10-21 16:50:33 +08:00
loginForm.code = ''
2025-11-13 17:01:39 +08:00
loginForm.password = ''
errors.email = errors.code = errors.password = errors.server = ''
// 重置倒计时
if (countdownTimer) {
clearInterval(countdownTimer)
countdownTimer = null
}
countdown.value = 0
2025-11-13 17:01:39 +08:00
// focus email input after clearing
await nextTick()
emailInput.value && emailInput.value.focus && emailInput.value.focus()
2025-10-21 16:50:33 +08:00
}
// 过滤验证码中的空格
const filterCodeSpaces = () => {
loginForm.code = loginForm.code.replace(/\s/g, '')
}
// 组件挂载时从URL参数读取邮箱
2025-10-21 16:50:33 +08:00
onMounted(() => {
// 从URL参数中读取邮箱
if (route.query.email) {
loginForm.email = route.query.email
}
2025-10-21 16:50:33 +08:00
})
// 获取邮箱验证码
const getEmailCode = async () => {
2025-11-13 17:01:39 +08:00
errors.email = ''
errors.code = ''
errors.password = ''
errors.server = ''
if (!loginForm.email) {
2025-11-13 17:01:39 +08:00
errors.email = '请输入邮箱地址'
emailInput.value && emailInput.value.focus && emailInput.value.focus()
2025-10-21 16:50:33 +08:00
return
}
2025-11-13 17:01:39 +08:00
if (!isEmailValid.value) {
errors.email = '请输入正确的邮箱地址'
emailInput.value && emailInput.value.focus && emailInput.value.focus()
2025-10-21 16:50:33 +08:00
return
}
try {
// 调用后端API发送邮箱验证码
2025-11-13 17:01:39 +08:00
const response = await sendEmailCode(loginForm.email)
2025-11-13 17:01:39 +08:00
if (response.data && response.data.success) {
ElMessage.success('验证码已发送到您的邮箱')
// 开始倒计时
startCountdown()
} else {
2025-11-13 17:01:39 +08:00
ElMessage.error(response.data?.message || '发送失败')
}
} catch (error) {
console.error('发送验证码失败:', error)
// 开发环境:显示真实验证码
if (process.env.NODE_ENV === 'development') {
// 生成6位随机验证码与后端逻辑一致
const randomCode = Array.from({length: 6}, () => Math.floor(Math.random() * 10)).join('')
// 开发模式:将验证码同步到后端
try {
2025-11-13 17:01:39 +08:00
await setDevEmailCode(loginForm.email, randomCode)
} catch (syncError) {
console.warn('同步验证码到后端失败:', syncError)
}
console.log(`📨 验证码已发送到: ${loginForm.email}`)
ElMessage.success(`验证码已发送到您的邮箱`)
startCountdown()
} else {
2025-11-13 17:01:39 +08:00
ElMessage.error(error.response?.data?.message || '网络错误,请稍后重试')
}
}
}
// 开始倒计时
const startCountdown = () => {
2025-10-21 16:50:33 +08:00
countdown.value = 60
countdownTimer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(countdownTimer)
countdownTimer = null
}
}, 1000)
}
const handleLogin = async () => {
2025-11-13 17:01:39 +08:00
// 基本邮箱校验
if (!loginForm.email) {
ElMessage.warning('请输入邮箱地址')
2025-10-21 16:50:33 +08:00
return
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(loginForm.email)) {
ElMessage.warning('请输入正确的邮箱地址')
2025-10-21 16:50:33 +08:00
return
}
2025-11-13 17:01:39 +08:00
2025-10-21 16:50:33 +08:00
try {
2025-11-13 17:01:39 +08:00
console.log('开始登录... 登录方式:', loginType)
let response = null
if (loginType.value === 'email') {
// 验证码登录
if (!loginForm.code) {
errors.code = '请输入验证码'
codeInput.value && codeInput.value.focus && codeInput.value.focus()
return
}
if (!isCodeValid.value) {
errors.code = '验证码格式不正确请输入6位数字'
codeInput.value && codeInput.value.focus && codeInput.value.focus()
return
}
response = await loginWithEmail({ email: loginForm.email, code: loginForm.code })
} else {
// 密码登录
if (!loginForm.password) {
errors.password = '请输入密码'
passwordInput.value && passwordInput.value.focus && passwordInput.value.focus()
return
}
2025-11-13 17:01:39 +08:00
if (!isPasswordValid.value) {
errors.password = '密码至少 6 位'
passwordInput.value && passwordInput.value.focus && passwordInput.value.focus()
return
}
response = await login({ email: loginForm.email, password: loginForm.password })
}
2025-11-13 17:01:39 +08:00
if (response && response.data && response.data.success) {
// 保存用户信息和token
const loginUser = response.data.data.user
const loginToken = response.data.data.token
const needsPasswordChange = response.data.data.needsPasswordChange // 后端直接返回是否需要修改密码
sessionStorage.setItem('token', loginToken)
sessionStorage.setItem('user', JSON.stringify(loginUser))
userStore.user = loginUser
userStore.token = loginToken
// 根据后端返回的标记设置是否需要修改密码
if (needsPasswordChange) {
sessionStorage.setItem('needSetPassword', '1')
console.log('新用户首次登录,需要设置密码')
} else {
sessionStorage.removeItem('needSetPassword')
}
2025-11-13 17:01:39 +08:00
console.log('登录成功,用户信息:', userStore.user, '需要设置密码:', needsPasswordChange)
2025-10-21 16:50:33 +08:00
ElMessage.success('登录成功')
2025-11-13 17:01:39 +08:00
2025-10-21 16:50:33 +08:00
// 等待一下确保状态更新
await new Promise(resolve => setTimeout(resolve, 200))
2025-11-13 17:01:39 +08:00
// 如果需要设置密码,优先跳转到个人主页,由个人主页负责弹出修改密码弹窗
const needSetPassword = sessionStorage.getItem('needSetPassword') === '1'
const redirectPath = needSetPassword ? '/profile' : (route.query.redirect || '/profile')
console.log('准备跳转到:', redirectPath, '需要设置密码:', needSetPassword)
2025-11-13 17:01:39 +08:00
2025-10-21 16:50:33 +08:00
// 使用replace而不是push避免浏览器历史记录问题
await router.replace(redirectPath)
2025-11-13 17:01:39 +08:00
2025-10-21 16:50:33 +08:00
console.log('路由跳转完成')
} else {
2025-11-13 17:01:39 +08:00
const msg = response?.data?.message || '登录失败'
errors.server = msg
ElMessage.error(msg)
2025-10-21 16:50:33 +08:00
}
} catch (error) {
console.error('Login error:', error)
2025-11-13 17:01:39 +08:00
const msg = error.response?.data?.message || '登录失败,请重试'
errors.server = msg
ElMessage.error(msg)
2025-10-21 16:50:33 +08:00
}
}
</script>
<style scoped>
.login-page {
min-height: 100vh;
width: 100vw;
height: 100vh;
background: #0a0e1a url('/images/backgrounds/login-bg.svg') center/cover no-repeat;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 0;
z-index: 1;
}
2025-10-21 16:50:33 +08:00
/* 左上角Logo */
.logo {
position: absolute;
top: 30px;
left: 30px;
z-index: 10;
}
2025-11-13 17:01:39 +08:00
.logo img {
height: 40px;
width: auto;
}
2025-10-21 16:50:33 +08:00
/* 登录卡片 */
.login-card {
position: absolute;
top: 50%;
2025-11-13 17:01:39 +08:00
right: 145px;
2025-10-21 16:50:33 +08:00
transform: translateY(-50%);
width: 773px;
height: 796px;
max-width: 90vw;
2025-11-13 17:01:39 +08:00
background: rgba(121, 121, 121, 0.1);
backdrop-filter: blur(50px);
-webkit-backdrop-filter: blur(50px);
2025-11-13 17:01:39 +08:00
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
padding: 80px 82px;
2025-10-21 16:50:33 +08:00
z-index: 10;
2025-11-13 17:01:39 +08:00
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: flex-start;
2025-11-13 17:01:39 +08:00
}
/* 欢迎标题 */
.welcome-title {
text-align: center;
margin-bottom: 50px;
font-size: 36px;
font-weight: 500;
2025-10-21 16:50:33 +08:00
}
.welcome-text {
color: rgba(255, 255, 255, 0.9);
margin-right: 8px;
2025-10-21 16:50:33 +08:00
}
.brand-name {
color: #00D4FF;
font-weight: 600;
2025-10-21 16:50:33 +08:00
}
/* 登录方式切换 */
.login-tabs {
2025-10-21 16:50:33 +08:00
display: flex;
justify-content: flex-start;
align-items: center;
margin-bottom: 50px;
gap: 0;
2025-10-21 16:50:33 +08:00
}
.tab-item {
padding: 10px 16px;
cursor: pointer;
transition: color 0.3s ease;
user-select: none;
color: #9EA9B6;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: 500;
letter-spacing: 2px;
}
.tab-item:hover {
color: rgba(255, 255, 255, 0.8);
2025-10-21 16:50:33 +08:00
}
.tab-item.active {
color: #ffffff;
2025-11-13 17:01:39 +08:00
}
.tab-divider {
width: 1px;
height: 24px;
background: #9EA9B6;
margin: 0 4px;
2025-11-13 17:01:39 +08:00
}
/* 登录表单 */
.login-form {
display: flex;
flex-direction: column;
gap: 25px;
2025-11-13 17:01:39 +08:00
}
/* 邮箱输入组 */
.email-input-group {
margin-bottom: 0;
2025-10-21 16:50:33 +08:00
}
.email-input {
width: 100%;
2025-10-21 16:50:33 +08:00
}
/* 快捷输入标签 */
.quick-email-tags {
display: flex;
gap: 8px;
margin-top: 10px;
flex-wrap: wrap;
}
.email-tag {
2025-11-13 17:01:39 +08:00
background: rgba(64, 158, 255, 0.1);
border: 1px solid rgba(64, 158, 255, 0.25);
color: #66B1FF;
padding: 6px 14px;
border-radius: 8px;
font-size: 13px;
cursor: pointer;
transition: all 0.3s ease;
user-select: none;
2025-11-13 17:01:39 +08:00
font-weight: 500;
}
.email-tag:hover {
2025-11-13 17:01:39 +08:00
background: rgba(64, 158, 255, 0.2);
border-color: rgba(64, 158, 255, 0.4);
color: #409EFF;
transform: translateY(-1px);
2025-11-13 17:01:39 +08:00
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
}
.email-input :deep(.el-input__wrapper) {
background: rgba(217, 217, 217, 0.2);
border: none;
2025-10-21 16:50:33 +08:00
border-radius: 10px;
box-shadow: none;
height: 80px;
2025-11-13 17:01:39 +08:00
transition: all 0.3s ease;
}
.email-input :deep(.el-input__wrapper:hover) {
background: rgba(217, 217, 217, 0.25);
2025-11-13 17:01:39 +08:00
}
.email-input :deep(.el-input__wrapper.is-focus) {
background: rgba(217, 217, 217, 0.3);
box-shadow: none;
2025-10-21 16:50:33 +08:00
}
.email-input :deep(.el-input__inner) {
color: rgba(255, 255, 255, 0.5);
2025-10-21 16:50:33 +08:00
background: transparent;
font-size: 16px;
}
.email-input :deep(.el-input__inner::placeholder) {
2025-10-21 16:50:33 +08:00
color: rgba(255, 255, 255, 0.5);
}
/* 验证码输入组 */
.code-input-wrapper {
margin-top: 30px;
2025-10-21 16:50:33 +08:00
}
.code-input :deep(.el-input__wrapper) {
background: rgba(217, 217, 217, 0.2);
border: none;
2025-10-21 16:50:33 +08:00
border-radius: 10px;
box-shadow: none;
height: 80px;
2025-11-13 17:01:39 +08:00
transition: all 0.3s ease;
padding-right: 15px;
2025-11-13 17:01:39 +08:00
}
.code-input :deep(.el-input__wrapper:hover) {
background: rgba(217, 217, 217, 0.25);
2025-11-13 17:01:39 +08:00
}
.code-input :deep(.el-input__wrapper.is-focus) {
background: rgba(217, 217, 217, 0.3);
box-shadow: none;
2025-10-21 16:50:33 +08:00
}
.code-input :deep(.el-input__inner) {
color: rgba(255, 255, 255, 0.5);
2025-10-21 16:50:33 +08:00
background: transparent;
font-size: 16px;
}
.code-input :deep(.el-input__inner::placeholder) {
color: rgba(255, 255, 255, 0.5);
}
.get-code-text {
color: #00D4FF;
font-size: 14px;
cursor: pointer;
user-select: none;
white-space: nowrap;
transition: opacity 0.3s ease;
2025-10-21 16:50:33 +08:00
}
.get-code-text:hover {
opacity: 0.8;
2025-10-21 16:50:33 +08:00
}
.get-code-text.disabled {
2025-10-21 16:50:33 +08:00
opacity: 0.5;
cursor: not-allowed;
}
.password-input-group {
margin-top: 30px;
}
.password-input-group :deep(.el-input__wrapper) {
background: rgba(217, 217, 217, 0.2);
border: none;
border-radius: 10px;
box-shadow: none;
height: 80px;
transition: all 0.3s ease;
}
.password-input-group :deep(.el-input__wrapper:hover) {
background: rgba(217, 217, 217, 0.25);
}
.password-input-group :deep(.el-input__wrapper.is-focus) {
background: rgba(217, 217, 217, 0.3);
box-shadow: none;
}
.password-input-group :deep(.el-input__inner) {
color: rgba(255, 255, 255, 0.5);
background: transparent;
font-size: 16px;
}
.password-input-group :deep(.el-input__inner::placeholder) {
color: rgba(255, 255, 255, 0.5);
}
2025-11-13 17:01:39 +08:00
.input-error {
color: #ff7875;
font-size: 12px;
margin-top: 6px;
text-align: left;
}
2025-10-21 16:50:33 +08:00
/* 登录按钮 */
.login-button {
width: 100%;
height: 80px;
background: #0DC0FF;
2025-10-21 16:50:33 +08:00
border: none;
2025-11-13 17:01:39 +08:00
border-radius: 10px;
2025-10-21 16:50:33 +08:00
color: white;
font-size: 18px;
font-weight: 500;
margin-top: 30px;
2025-10-21 16:50:33 +08:00
transition: all 0.3s ease;
}
.login-button:hover {
background: #4DD4FF;
transform: translateY(-1px);
2025-10-21 16:50:33 +08:00
}
.login-button:active {
transform: translateY(0);
}
/* 协议文字 */
.agreement-text {
text-align: center;
color: rgba(255, 255, 255, 0.4);
2025-10-21 16:50:33 +08:00
font-size: 12px;
margin: 20px 0 0 0;
line-height: 30px;
width: 266px;
height: 30px;
margin-left: auto;
margin-right: auto;
2025-10-21 16:50:33 +08:00
}
.agreement-link {
color: #00D4FF;
text-decoration: none;
transition: opacity 0.3s ease;
2025-10-21 16:50:33 +08:00
}
.agreement-link:hover {
opacity: 0.8;
text-decoration: underline;
2025-10-21 16:50:33 +08:00
}
/* 响应式设计 */
@media (max-width: 1200px) {
.login-card {
right: 5%;
width: 450px;
height: auto;
2025-10-21 16:50:33 +08:00
}
}
@media (max-width: 768px) {
.login-card {
position: relative;
top: auto;
left: auto;
2025-10-21 16:50:33 +08:00
transform: none;
margin: 50px auto;
width: 90%;
max-width: 500px;
height: auto;
2025-10-21 16:50:33 +08:00
}
.logo {
position: relative;
top: auto;
left: auto;
text-align: center;
margin-bottom: 30px;
padding-top: 30px;
}
}
@media (max-width: 480px) {
.login-card {
padding: 40px 25px;
height: auto;
2025-10-21 16:50:33 +08:00
}
.code-input-group {
flex-direction: column;
gap: 15px;
}
}
</style>