iGuwan-homework/src/views/auth/LoginView.vue

604 lines
12 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-container">
<!-- 背景动画 -->
<div class="background-animation">
<div class="floating-shapes">
<div v-for="i in 8" :key="i" class="shape" :style="getShapeStyle(i)"></div>
</div>
<div class="grid-lines">
<div class="grid-line horizontal" v-for="i in 6" :key="'h' + i" :style="{ top: i * 20 + '%' }"></div>
<div class="grid-line vertical" v-for="i in 8" :key="'v' + i" :style="{ left: i * 12.5 + '%' }"></div>
</div>
</div>
<div class="login-content">
<div class="login-card">
<!-- Logo区域 -->
<div class="logo-section">
<div class="logo-icon">📚</div>
<h1 class="logo-title">智慧学习</h1>
<p class="logo-subtitle">在线作业管理系统</p>
</div>
<!-- 登录表单 -->
<form @submit.prevent="handleLogin" class="login-form">
<div class="form-group">
<label class="form-label">用户名/邮箱</label>
<div class="input-wrapper">
<input
v-model="loginForm.username"
type="text"
class="form-input"
placeholder="请输入用户名或邮箱"
required
>
<div class="input-icon">👤</div>
</div>
</div>
<div class="form-group">
<label class="form-label">密码</label>
<div class="input-wrapper">
<input
v-model="loginForm.password"
:type="showPassword ? 'text' : 'password'"
class="form-input"
placeholder="请输入密码"
required
>
<button
type="button"
class="password-toggle"
@click="showPassword = !showPassword"
>
{{ showPassword ? '🙈' : '👁️' }}
</button>
</div>
</div>
<div class="form-options">
<label class="checkbox-wrapper">
<input v-model="loginForm.rememberMe" type="checkbox" class="checkbox">
<span class="checkbox-label">记住我</span>
</label>
<a href="#" class="forgot-link">忘记密码?</a>
</div>
<button type="submit" class="login-btn" :disabled="isLoading">
<span v-if="!isLoading">登录</span>
<span v-else class="loading-spinner">🔄</span>
</button>
</form>
<!-- 分割线 -->
<div class="divider">
<span class="divider-text"></span>
</div>
<!-- 第三方登录 -->
<div class="social-login">
<button class="social-btn wechat">
<span class="social-icon">💬</span>
<span>微信登录</span>
</button>
<button class="social-btn qq">
<span class="social-icon">🐧</span>
<span>QQ登录</span>
</button>
</div>
<!-- 注册链接 -->
<div class="register-section">
<span class="register-text">还没有账号</span>
<router-link to="/register" class="register-link">立即注册</router-link>
</div>
</div>
<!-- 功能特色 -->
<div class="features-section">
<h3 class="features-title">为什么选择我们</h3>
<div class="features-list">
<div class="feature-item">
<div class="feature-icon"></div>
<div class="feature-content">
<h4 class="feature-name">高效管理</h4>
<p class="feature-desc">智能作业分配与进度跟踪</p>
</div>
</div>
<div class="feature-item">
<div class="feature-icon">📊</div>
<div class="feature-content">
<h4 class="feature-name">数据分析</h4>
<p class="feature-desc">详细的学习数据统计分析</p>
</div>
</div>
<div class="feature-item">
<div class="feature-icon">🎯</div>
<div class="feature-content">
<h4 class="feature-name">个性化</h4>
<p class="feature-desc">根据能力定制学习计划</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// 表单数据
const loginForm = reactive({
username: '',
password: '',
rememberMe: false
})
const showPassword = ref(false)
const isLoading = ref(false)
// 登录处理
const handleLogin = async () => {
isLoading.value = true
try {
// 模拟登录API调用
await new Promise(resolve => setTimeout(resolve, 1500))
// 登录成功,跳转到首页
router.push('/')
} catch (error) {
console.error('登录失败:', error)
} finally {
isLoading.value = false
}
}
// 浮动形状样式
const getShapeStyle = (index: number) => {
const shapes = ['🔷', '🔶', '⭐', '🌟', '💎', '🔸', '🔹', '✨']
const size = Math.random() * 30 + 20
const left = Math.random() * 100
const top = Math.random() * 100
const animationDelay = Math.random() * 5
const animationDuration = Math.random() * 3 + 4
return {
fontSize: `${size}px`,
left: `${left}%`,
top: `${top}%`,
animationDelay: `${animationDelay}s`,
animationDuration: `${animationDuration}s`,
'--content': `'${shapes[index % shapes.length]}'`
}
}
</script>
<style scoped>
.login-container {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.background-animation {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
.floating-shapes {
position: absolute;
width: 100%;
height: 100%;
}
.shape {
position: absolute;
opacity: 0.1;
animation: float linear infinite;
}
.shape::before {
content: var(--content);
}
@keyframes float {
0% {
transform: translateY(100vh) rotate(0deg);
opacity: 0;
}
10% {
opacity: 0.1;
}
90% {
opacity: 0.1;
}
100% {
transform: translateY(-100px) rotate(360deg);
opacity: 0;
}
}
.grid-lines {
position: absolute;
width: 100%;
height: 100%;
}
.grid-line {
position: absolute;
background: rgba(255, 255, 255, 0.05);
animation: gridPulse 4s ease-in-out infinite;
}
.grid-line.horizontal {
width: 100%;
height: 1px;
}
.grid-line.vertical {
width: 1px;
height: 100%;
}
@keyframes gridPulse {
0%, 100% {
opacity: 0.05;
}
50% {
opacity: 0.15;
}
}
.login-content {
position: relative;
z-index: 2;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
max-width: 1000px;
width: 100%;
}
.login-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 24px;
padding: 3rem;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.logo-section {
text-align: center;
margin-bottom: 2rem;
}
.logo-icon {
font-size: 4rem;
margin-bottom: 1rem;
}
.logo-title {
font-size: 2.5rem;
font-weight: 700;
color: white;
margin: 0 0 0.5rem 0;
text-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.logo-subtitle {
font-size: 1.1rem;
color: rgba(255, 255, 255, 0.8);
margin: 0;
}
.login-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-label {
font-size: 0.9rem;
font-weight: 600;
color: rgba(255, 255, 255, 0.9);
}
.input-wrapper {
position: relative;
}
.form-input {
width: 100%;
padding: 1rem 3rem 1rem 1rem;
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
background: rgba(255, 255, 255, 0.1);
color: white;
font-size: 1rem;
transition: all 0.3s ease;
box-sizing: border-box;
}
.form-input::placeholder {
color: rgba(255, 255, 255, 0.5);
}
.form-input:focus {
outline: none;
border-color: rgba(79, 172, 254, 0.8);
background: rgba(255, 255, 255, 0.15);
box-shadow: 0 0 20px rgba(79, 172, 254, 0.3);
}
.input-icon {
position: absolute;
right: 1rem;
top: 50%;
transform: translateY(-50%);
font-size: 1.2rem;
opacity: 0.6;
}
.password-toggle {
position: absolute;
right: 1rem;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
font-size: 1.2rem;
cursor: pointer;
opacity: 0.6;
transition: opacity 0.3s ease;
}
.password-toggle:hover {
opacity: 1;
}
.form-options {
display: flex;
justify-content: space-between;
align-items: center;
}
.checkbox-wrapper {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
}
.checkbox {
width: 18px;
height: 18px;
accent-color: #4facfe;
}
.checkbox-label {
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.8);
}
.forgot-link {
font-size: 0.9rem;
color: #4facfe;
text-decoration: none;
transition: color 0.3s ease;
}
.forgot-link:hover {
color: #00f2fe;
}
.login-btn {
width: 100%;
padding: 1rem;
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
border: none;
border-radius: 12px;
color: white;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.login-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(79, 172, 254, 0.4);
}
.login-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.loading-spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.divider {
position: relative;
text-align: center;
margin: 2rem 0;
}
.divider::before {
content: '';
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 1px;
background: rgba(255, 255, 255, 0.2);
}
.divider-text {
background: rgba(255, 255, 255, 0.1);
padding: 0 1rem;
color: rgba(255, 255, 255, 0.6);
font-size: 0.9rem;
}
.social-login {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 2rem;
}
.social-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.8rem;
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
background: rgba(255, 255, 255, 0.05);
color: white;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s ease;
}
.social-btn:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.3);
}
.social-icon {
font-size: 1.2rem;
}
.register-section {
text-align: center;
}
.register-text {
color: rgba(255, 255, 255, 0.7);
font-size: 0.9rem;
}
.register-link {
color: #4facfe;
text-decoration: none;
font-weight: 600;
margin-left: 0.5rem;
transition: color 0.3s ease;
}
.register-link:hover {
color: #00f2fe;
}
.features-section {
display: flex;
flex-direction: column;
justify-content: center;
}
.features-title {
font-size: 2rem;
font-weight: 700;
color: white;
margin-bottom: 2rem;
text-align: center;
}
.features-list {
display: flex;
flex-direction: column;
gap: 2rem;
}
.feature-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 1.5rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 16px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
transition: all 0.3s ease;
}
.feature-item:hover {
transform: translateX(10px);
background: rgba(255, 255, 255, 0.15);
}
.feature-icon {
font-size: 2.5rem;
width: 60px;
text-align: center;
}
.feature-content {
flex: 1;
}
.feature-name {
font-size: 1.2rem;
font-weight: 600;
color: white;
margin: 0 0 0.5rem 0;
}
.feature-desc {
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.7);
margin: 0;
line-height: 1.4;
}
@media (max-width: 768px) {
.login-content {
grid-template-columns: 1fr;
gap: 2rem;
}
.login-card {
padding: 2rem;
}
.logo-title {
font-size: 2rem;
}
.features-title {
font-size: 1.5rem;
}
}
</style>