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

734 lines
24 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">
2025-10-21 16:50:33 +08:00
<!-- Logo图标 -->
<div class="card-logo">
2025-11-13 17:01:39 +08:00
<svg width="306" height="37" viewBox="0 0 306 37" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.266 9.22263e-06L23.218 0.64601C22.952 2.54601 22.648 4.33201 22.268 5.96601H34.276V8.58801C33.668 11.818 32.946 15.048 32.034 18.24L28.158 17.138C28.918 15.048 29.564 12.616 30.096 9.80401H21.242C20.292 12.616 19.152 14.896 17.784 16.644L14.592 14.402C17.024 10.982 18.582 6.15601 19.266 9.22263e-06ZM13.11 29.792C11.97 28.044 10.83 26.334 9.65201 24.662C7.52401 28.766 4.97801 32.034 1.97601 34.466L7.80571e-06 30.894C3.00201 28.69 5.43401 25.536 7.29601 21.432C5.16801 18.658 2.96401 15.96 0.608008 13.3L3.23001 10.792C4.94001 12.502 6.84001 14.668 8.89201 17.252C9.72801 14.516 10.412 11.552 10.944 8.32201H0.684008V4.48401H14.82V8.13201C13.984 12.768 12.844 16.91 11.476 20.558C12.92 22.496 14.402 24.586 15.998 26.866L13.11 29.792ZM22.762 11.894H26.676C26.676 14.516 26.524 16.91 26.22 19.114C28.044 24.738 31.122 29.26 35.53 32.642L32.87 35.758C29.45 32.756 26.828 29.146 24.928 24.928C23.56 29.298 20.406 32.908 15.504 35.758L12.996 32.68C17.176 30.172 19.912 27.17 21.204 23.636C22.192 20.596 22.724 16.682 22.762 11.894ZM41.762 0.83601C44.308 2.81201 46.474 4.82601 48.26 6.84001L45.524 9.57601C44.004 7.67601 41.876 5.58601 39.102 3.34401L41.762 0.83601ZM64.486 35.036C62.32 35.036 59.964 34.998 57.418 34.96C54.834 34.922 52.706 34.694 51.072 34.276C49.438 33.82 48.07 32.946 46.93 31.654C46.398 30.97 45.866 30.666 45.41 30.666C44.65 30.666 43.282 32.452 41.306 36.062L38.494 33.402C40.394 30.286 42.104 28.272 43.7 27.436V16.91H38.57V13.338H47.31V27.702C47.462 27.854 47.652 28.006 47.842 28.234C48.792 29.26 49.704 30.02 50.616 30.514C51.794 31.046 53.542 31.388 55.898 31.464C58.178 31.502 60.876 31.54 63.992 31.54C65.74 31.54 67.488 31.502 69.312 31.464C71.136 31.388 72.504 31.35 73.492 31.274L72.58 35.036H64.486ZM50.198 27.74L49.134 24.244C49.818 23.94 50.198 23.294 50.198 22.344V3.91401C53.466 2.88801 56.05 1.67201 58.026 0.30401L60.23 3.49601C58.634 4.56001 56.544 5.58601 53.884 6.53601V23.066C55.632 22.572 57.304 22.04 58.976 21.47L59.698 25.194C56.962 26.03 53.808 26.866 50.198 27.74ZM68.59 26.448H65.778L64.714 22.686L67.336 22.876C68.02 22.876 68.362 22.458 68.362 21.66V6.00401H64.334V30.514H60.534V2.50801H72.086V22.458C72.086 25.118 70.908 26.448 68.59 26.448ZM84.93 9.15801C86.07 11.02 87.21 13.11 88.312 15.39L85.614 16.758H91.694V8.43601H78.47V4.59801H91.694V0.64601H95.722V4.59801H109.288V8.43601H95.722V16.758H98.762C100.054 14.288 101.156 11.666 102.106 8.89201L105.982 10.26C104.994 12.654 103.892 14.82 102.676 16.758H110.352V20.596H98.762C101.422 24.434 105.716 27.892 111.644 30.932L108.908 34.352C102.752 30.324 98.344 25.84 95.722 20.938V35.644H91.694V20.9C89.224 26.144 84.664 30.628 77.976 34.39L75.772 30.818C82.004 27.74 86.298 24.32 88.578 20.596H76.988V16.758H84.778C83.714 14.516 82.612 12.54 81.434 10.83L84.93 9.15801ZM134.33 28.158V32.11C128.402 33.326 122.056 34.39 115.292 35.226L114.342 31.426C117.268 31.122 120.156 30.78 122.968 30.362V24.434H116.09V20.634H122.968V16.796H126.958V20.634H133.228V24.434H126.958V29.678C129.466 29.222 131.898 28.69 134.33 28.158ZM114.988 1.78601H134.178V5.51001H124.716C123.196 8.58801 121.828 10.982 120.612 12.654C123.348 12.35 126.084 12.008 128.82 11.552C128.136 10.488 127.414 9.38601 126.654 8.28401L129.694 6.34601C132.202 9.65201 134.102 12.54 135.432 14.972L132.126 17.252C131.67 16.34 131.176 15.428 130.644 14.516C125.894 15.352 120.726 16.036 115.178 16.606L114.304 13.072C114.988 12.996 115.482 12.844 115.862 12.692C116.926 11.818 118.484 9.42401 120.46 5.51001H114.988V1.78601ZM144.02 35.34H138.358L137.484 31.502C139.308 31.654 141.018 31.73 142.652 31.73C143.526 31.73 143.982 31.236 143.982 30.324V0.64601H148.01V31.35C148.01 34.01 146.68 35.34 144.02 35.34ZM136.686 4.06601H140.524V27.284H136.686V4.06601Z" fill="white"/>
<path d="M163.552 4.59801H168.378L175.826 26.714H175.94L183.388 4.59801H188.214L178.562 31.73H173.204L163.552 4.59801ZM193.617 4.06601C194.453 4.06601 195.175 4.33201 195.745 4.86401C196.277 5.39601 196.581 6.08001 196.581 6.91601C196.581 7.75201 196.277 8.47401 195.707 9.00601C195.137 9.53801 194.453 9.80401 193.617 9.80401C192.781 9.80401 192.097 9.53801 191.527 9.00601C190.957 8.43601 190.691 7.75201 190.691 6.91601C190.691 6.08001 190.957 5.39601 191.527 4.86401C192.097 4.33201 192.781 4.06601 193.617 4.06601ZM191.451 12.084H195.783V31.73H191.451V12.084ZM215.14 4.06601H219.472V31.73H215.444V29.64C214.076 31.388 212.1 32.262 209.516 32.262C206.59 32.262 204.31 31.236 202.676 29.184C201.156 27.284 200.396 24.814 200.396 21.812C200.396 18.924 201.118 16.53 202.638 14.63C204.234 12.578 206.476 11.552 209.288 11.552C211.568 11.552 213.506 12.616 215.14 14.782V4.06601ZM210.314 15.048C208.338 15.048 206.932 15.694 206.02 16.986C205.222 18.088 204.842 19.684 204.842 21.812C204.842 23.94 205.184 25.574 205.944 26.714C206.818 28.082 208.224 28.766 210.162 28.766C211.834 28.766 213.164 28.082 214.076 26.752C214.874 25.536 215.292 23.94 215.292 22.04V21.736C215.292 19.646 214.76 17.974 213.772 16.758C212.86 15.618 211.682 15.048 210.314 15.048ZM224.395 4.59801H242.901V8.39801H228.841V15.922H242.103V19.722H228.841V31.73H224.395V4.59801ZM246.513 4.06601H250.845V31.73H246.513V4.06601ZM264.901 11.552C267.865 11.552 270.259 12.502 272.083 14.478C273.869 16.416 274.781 18.886 274.781 21.926C274.781 24.928 273.869 27.398 272.121 29.298C270.297 31.274 267.865 32.262 264.901 32.262C261.899 32.262 259.505 31.274 257.681 29.298C255.895 27.398 255.021 24.928 255.021 21.926C255.021 18.886 255.895 16.416 257.719 14.478C259.505 12.502 261.899 11.552 264.901 11.552ZM264.901 15.086C263.077 15.086 261.709 15.77 260.721 17.214C259.885 18.43 259.467 20.026 259.467 21.926C259.467 23.826 259.885 25.384 260.721 26.6C261.709 28.006 263.077 28.728 264.901 28.728C266.687 28.728 268.093 28.006 269.081 26.6C269.917 25.346 270.373 23.788 270.373 21.926C270.373 20.026 269.917 18.43 269.081 17.214C268.093 15.77 266.687 15.086 264.901 15.086ZM276.476 12.084H281.264L285.178 26.6L289.054 12.084H293.044L296.92 26.6L300.834 12.084H305.622L299.01 31.73H294.982L291.068 17.366L287.116 31.73H283.088L276.476 12.084Z" fill="#0DC0FF"/>
</svg>
2025-10-21 16:50:33 +08:00
</div>
2025-11-13 17:01:39 +08:00
<!-- 登录标题 -->
<div class="login-title">
2025-11-13 17:01:39 +08:00
<div class="login-methods">
<button :class="['method-btn', { active: loginType === 'email' }]" @click="() => { loginType = 'email'; clearForm(); }">邮箱验证码登录</button>
<button :class="['method-btn', { active: loginType === 'password' }]" @click="() => { loginType = 'password'; clearForm(); }">邮箱密码登录</button>
</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 class="quick-email-tags">
<span class="email-tag" @click="fillQuickEmail('984523799@qq.com')">984523799@qq.com</span>
</div>
</div>
2025-11-13 17:01:39 +08:00
<!-- 验证码输入仅验证码登录显示 -->
<div class="code-input-group" 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"
/>
2025-11-13 17:01:39 +08:00
<div class="input-error" v-if="errors.code">{{ errors.code }}</div>
<el-button
type="primary"
plain
class="get-code-btn"
2025-11-13 17:01:39 +08:00
:disabled="countdown > 0 || !isEmailValid"
@click="getEmailCode"
>
{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
</el-button>
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"
>
2025-11-13 17:01:39 +08:00
{{ userStore.loading ? '登录中...' : (loginType === 'password' ? '登录' : '登陆/注册') }}
2025-10-21 16:50:33 +08:00
</el-button>
<!-- 协议文字 -->
<p class="agreement-text">
登录即表示您同意遵守用户协议和隐私政策
</p>
<!-- 测试邮箱提示 -->
2025-10-21 16:50:33 +08:00
<div class="test-accounts">
<el-divider>测试邮箱</el-divider>
2025-10-21 16:50:33 +08:00
<div class="account-list">
<div class="account-item" @click="fillTestAccount('admin@example.com', '123456')">
<strong>管理员:</strong> admin@example.com
2025-10-21 16:50:33 +08:00
</div>
<div class="account-item" @click="fillTestAccount('13689270819@example.com', '123456')">
<strong>普通用户:</strong> 13689270819@example.com
2025-10-21 16:50:33 +08:00
</div>
</div>
</div>
</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'
2025-11-13 17:01:39 +08:00
import { loginWithEmail, login, sendEmailCode, setDevEmailCode } 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
}
2025-10-21 16:50:33 +08:00
// 快速填充测试账号
2025-11-13 17:01:39 +08:00
const fillTestAccount = async (email, code) => {
loginForm.email = email
2025-11-13 17:01:39 +08:00
// 根据当前登录方式,将第二个参数作为验证码或密码
if (loginType.value === 'password') {
loginForm.password = code
await nextTick()
passwordInput.value && passwordInput.value.focus && passwordInput.value.focus()
} else {
loginForm.code = code
await nextTick()
codeInput.value && codeInput.value.focus && codeInput.value.focus()
}
2025-10-21 16:50:33 +08:00
}
// 快速填充邮箱(快捷输入)
const fillQuickEmail = (email) => {
loginForm.email = email
}
// 组件挂载时设置默认测试账号或从URL参数读取邮箱
2025-10-21 16:50:33 +08:00
onMounted(() => {
// 从URL参数中读取邮箱
if (route.query.email) {
loginForm.email = route.query.email
} else {
// 设置默认的测试邮箱
loginForm.email = 'admin@example.com'
}
// 不设置验证码,让用户手动输入
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
sessionStorage.setItem('token', response.data.data.token)
sessionStorage.setItem('user', JSON.stringify(response.data.data.user))
userStore.user = response.data.data.user
userStore.token = response.data.data.token
2025-10-21 16:50:33 +08:00
console.log('登录成功,用户信息:', userStore.user)
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
2025-10-21 16:50:33 +08:00
// 跳转到原始路径或个人主页
const redirectPath = route.query.redirect || '/profile'
console.log('准备跳转到:', redirectPath)
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;
2025-11-13 17:01:39 +08:00
background: url('/images/backgrounds/login-bg.svg') center/cover no-repeat;
position: fixed;
top: 0;
left: 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: 800px;
max-width: 90vw;
2025-11-13 17:01:39 +08:00
background: rgba(121, 121, 121, 0.1);
backdrop-filter: blur(50px) saturate(180%);
-webkit-backdrop-filter: blur(50px) saturate(180%);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
2025-11-13 17:01:39 +08:00
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.5),
0 0 0 1px rgba(255, 255, 255, 0.05) inset;
2025-10-21 16:50:33 +08:00
padding: 50px;
z-index: 10;
2025-11-13 17:01:39 +08:00
overflow: hidden;
}
.login-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.15), transparent);
opacity: 0.8;
2025-10-21 16:50:33 +08:00
}
/* 卡片内Logo */
.card-logo {
text-align: center;
2025-11-13 17:01:39 +08:00
margin-bottom: 40px;
2025-10-21 16:50:33 +08:00
display: flex;
justify-content: center;
2025-11-13 17:01:39 +08:00
align-items: center;
2025-10-21 16:50:33 +08:00
}
2025-11-13 17:01:39 +08:00
.card-logo svg {
width: auto;
height: 40px;
2025-10-21 16:50:33 +08:00
}
/* 登录表单 */
.login-form {
display: flex;
flex-direction: column;
gap: 25px;
}
/* 登录标题 */
.login-title {
text-align: center;
2025-11-13 17:01:39 +08:00
margin-bottom: 20px;
}
.login-subtitle {
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
margin: 0;
text-align: center;
2025-10-21 16:50:33 +08:00
}
2025-11-13 17:01:39 +08:00
/* 登录方式切换 */
.login-methods {
display: inline-flex;
gap: 8px;
}
.method-btn {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
color: rgba(255, 255, 255, 0.7);
padding: 8px 16px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
font-weight: 500;
}
.method-btn:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.12);
color: rgba(255, 255, 255, 0.85);
}
.method-btn.active {
background: rgba(64, 158, 255, 0.15);
border-color: rgba(64, 158, 255, 0.3);
color: #66B1FF;
}
.password-input-group {
margin-top: 10px;
}
2025-10-21 16:50:33 +08:00
/* 邮箱输入组 */
.email-input-group {
margin-bottom: 20px;
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) {
2025-11-13 17:01:39 +08:00
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
2025-10-21 16:50:33 +08:00
border-radius: 10px;
2025-11-13 17:01:39 +08:00
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
2025-10-21 16:50:33 +08:00
height: 55px;
2025-11-13 17:01:39 +08:00
transition: all 0.3s ease;
}
.email-input :deep(.el-input__wrapper:hover) {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.15);
}
.email-input :deep(.el-input__wrapper.is-focus) {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(64, 158, 255, 0.4);
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.1);
2025-10-21 16:50:33 +08:00
}
.email-input :deep(.el-input__inner) {
2025-10-21 16:50:33 +08:00
color: white;
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-group {
display: flex;
gap: 12px;
}
.code-input {
flex: 1;
}
.code-input :deep(.el-input__wrapper) {
2025-11-13 17:01:39 +08:00
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
2025-10-21 16:50:33 +08:00
border-radius: 10px;
2025-11-13 17:01:39 +08:00
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
2025-10-21 16:50:33 +08:00
height: 55px;
2025-11-13 17:01:39 +08:00
transition: all 0.3s ease;
}
.code-input :deep(.el-input__wrapper:hover) {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.15);
}
.code-input :deep(.el-input__wrapper.is-focus) {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(64, 158, 255, 0.4);
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.1);
2025-10-21 16:50:33 +08:00
}
.code-input :deep(.el-input__inner) {
color: white;
background: transparent;
font-size: 16px;
}
.code-input :deep(.el-input__inner::placeholder) {
color: rgba(255, 255, 255, 0.5);
}
.get-code-btn {
2025-11-13 17:01:39 +08:00
background: rgba(64, 158, 255, 0.12);
border: 1px solid rgba(64, 158, 255, 0.3);
color: #66B1FF;
2025-10-21 16:50:33 +08:00
border-radius: 10px;
padding: 0 20px;
font-size: 16px;
height: 55px;
transition: all 0.3s ease;
2025-11-13 17:01:39 +08:00
font-weight: 500;
2025-10-21 16:50:33 +08:00
}
.get-code-btn:hover {
2025-11-13 17:01:39 +08:00
background: rgba(64, 158, 255, 0.85);
border-color: rgba(64, 158, 255, 0.8);
2025-10-21 16:50:33 +08:00
color: white;
2025-11-13 17:01:39 +08:00
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.25);
2025-10-21 16:50:33 +08:00
}
.get-code-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
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%;
2025-11-13 17:01:39 +08:00
height: 52px;
background: linear-gradient(135deg, #409EFF 0%, #66B1FF 100%);
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: 16px;
2025-11-13 17:01:39 +08:00
font-weight: 600;
2025-10-21 16:50:33 +08:00
margin-top: 15px;
transition: all 0.3s ease;
2025-11-13 17:01:39 +08:00
box-shadow: 0 4px 16px rgba(64, 158, 255, 0.25);
2025-10-21 16:50:33 +08:00
}
.login-button:hover {
2025-11-13 17:01:39 +08:00
background: linear-gradient(135deg, #66B1FF 0%, #409EFF 100%);
2025-10-21 16:50:33 +08:00
transform: translateY(-2px);
2025-11-13 17:01:39 +08:00
box-shadow: 0 6px 20px rgba(64, 158, 255, 0.35);
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.5);
font-size: 14px;
margin: 25px 0 0 0;
line-height: 1.4;
}
/* 测试账号提示 */
.test-accounts {
margin-top: 30px;
}
.test-accounts :deep(.el-divider__text) {
color: rgba(255, 255, 255, 0.6);
font-size: 12px;
}
.account-list {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 15px;
}
.account-item {
font-size: 12px;
2025-11-13 17:01:39 +08:00
color: rgba(255, 255, 255, 0.75);
padding: 8px 14px;
background: rgba(255, 255, 255, 0.04);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.08);
2025-10-21 16:50:33 +08:00
cursor: pointer;
transition: all 0.3s ease;
}
.account-item:hover {
2025-11-13 17:01:39 +08:00
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.12);
2025-10-21 16:50:33 +08:00
transform: translateY(-1px);
2025-11-13 17:01:39 +08:00
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
2025-10-21 16:50:33 +08:00
}
.account-item strong {
color: #409EFF;
margin-right: 8px;
}
/* 响应式设计 */
@media (max-width: 1200px) {
.login-card {
right: 5%;
width: 450px;
}
}
@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;
}
.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;
}
.code-input-group {
flex-direction: column;
gap: 15px;
}
}
</style>