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

803 lines
19 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="register-container">
<!-- 背景动画 -->
<div class="background-animation">
<div class="floating-shapes">
<div v-for="i in 10" :key="i" class="shape" :style="getShapeStyle(i)"></div>
</div>
<div class="particle-system">
<div v-for="i in 20" :key="i" class="particle" :style="getParticleStyle(i)"></div>
</div>
</div>
<div class="register-content">
<div class="register-card">
<!-- 头部 -->
<div class="header-section">
<div class="logo-icon">🎓</div>
<h1 class="title">创建账号</h1>
<p class="subtitle">加入智慧学习平台,开启学习之旅</p>
</div>
<!-- 注册表单 -->
<form @submit.prevent="handleRegister" class="register-form">
<!-- 基本信息 -->
<div class="form-section">
<h3 class="section-title">基本信息</h3>
<div class="form-row">
<div class="form-group">
<label class="form-label">姓名</label>
<div class="input-wrapper">
<input
v-model="registerForm.name"
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="registerForm.username"
type="text"
class="form-input"
placeholder="设置用户名"
required
>
<div class="input-icon">🏷️</div>
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">邮箱地址</label>
<div class="input-wrapper">
<input
v-model="registerForm.email"
type="email"
class="form-input"
placeholder="请输入邮箱地址"
required
>
<div class="input-icon">📧</div>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">密码</label>
<div class="input-wrapper">
<input
v-model="registerForm.password"
:type="showPassword ? 'text' : 'password'"
class="form-input"
placeholder="设置密码"
required
>
<button
type="button"
class="password-toggle"
@click="showPassword = !showPassword"
>
{{ showPassword ? '🙈' : '👁️' }}
</button>
</div>
<div class="password-strength">
<div class="strength-bar" :class="passwordStrength.class">
<div class="strength-fill" :style="{ width: passwordStrength.width }"></div>
</div>
<span class="strength-text">{{ passwordStrength.text }}</span>
</div>
</div>
<div class="form-group">
<label class="form-label">确认密码</label>
<div class="input-wrapper">
<input
v-model="registerForm.confirmPassword"
:type="showConfirmPassword ? 'text' : 'password'"
class="form-input"
placeholder="再次输入密码"
required
>
<button
type="button"
class="password-toggle"
@click="showConfirmPassword = !showConfirmPassword"
>
{{ showConfirmPassword ? '🙈' : '👁️' }}
</button>
</div>
<div v-if="registerForm.confirmPassword && !passwordsMatch" class="error-message">
密码不匹配
</div>
</div>
</div>
</div>
<!-- 个人信息 -->
<div class="form-section">
<h3 class="section-title">个人信息</h3>
<div class="form-row">
<div class="form-group">
<label class="form-label">身份</label>
<select v-model="registerForm.role" class="form-select" required>
<option value="">请选择身份</option>
<option value="student">学生</option>
<option value="teacher">教师</option>
<option value="parent">家长</option>
</select>
</div>
<div class="form-group">
<label class="form-label">年级/学科</label>
<select v-model="registerForm.grade" class="form-select">
<option value="">请选择</option>
<option v-for="grade in gradeOptions" :key="grade" :value="grade">{{ grade }}</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label">学校/机构</label>
<div class="input-wrapper">
<input
v-model="registerForm.school"
type="text"
class="form-input"
placeholder="请输入学校或机构名称"
>
<div class="input-icon">🏫</div>
</div>
</div>
</div>
<!-- 协议同意 -->
<div class="agreement-section">
<label class="checkbox-wrapper">
<input v-model="registerForm.agreeTerms" type="checkbox" class="checkbox" required>
<span class="checkbox-label">
我已阅读并同意
<a href="#" class="link">用户协议</a>
<a href="#" class="link">隐私政策</a>
</span>
</label>
</div>
<!-- 提交按钮 -->
<button type="submit" class="register-btn" :disabled="!canSubmit || isLoading">
<span v-if="!isLoading">创建账号</span>
<span v-else class="loading-spinner">🔄</span>
</button>
</form>
<!-- 登录链接 -->
<div class="login-section">
<span class="login-text">已有账号?</span>
<router-link to="/login" class="login-link">立即登录</router-link>
</div>
</div>
<!-- 优势展示 -->
<div class="benefits-section">
<h3 class="benefits-title">加入我们的优势</h3>
<div class="benefits-grid">
<div class="benefit-card">
<div class="benefit-icon">🚀</div>
<h4 class="benefit-title">快速上手</h4>
<p class="benefit-desc">简单易用的界面设计5分钟即可熟练操作</p>
</div>
<div class="benefit-card">
<div class="benefit-icon">📈</div>
<h4 class="benefit-title">学习追踪</h4>
<p class="benefit-desc">实时跟踪学习进度,科学分析学习效果</p>
</div>
<div class="benefit-card">
<div class="benefit-icon">🎯</div>
<h4 class="benefit-title">个性化</h4>
<p class="benefit-desc">AI智能推荐为每个学生定制专属学习路径</p>
</div>
<div class="benefit-card">
<div class="benefit-icon">🤝</div>
<h4 class="benefit-title">协作学习</h4>
<p class="benefit-desc">师生互动,同学协作,营造良好学习氛围</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// 表单数据
const registerForm = reactive({
name: '',
username: '',
email: '',
password: '',
confirmPassword: '',
role: '',
grade: '',
school: '',
agreeTerms: false
})
const showPassword = ref(false)
const showConfirmPassword = ref(false)
const isLoading = ref(false)
// 年级选项
const gradeOptions = computed(() => {
if (registerForm.role === 'student') {
return ['小学一年级', '小学二年级', '小学三年级', '小学四年级', '小学五年级', '小学六年级',
'初中一年级', '初中二年级', '初中三年级', '高中一年级', '高中二年级', '高中三年级']
} else if (registerForm.role === 'teacher') {
return ['语文', '数学', '英语', '物理', '化学', '生物', '历史', '地理', '政治', '体育', '音乐', '美术']
} else if (registerForm.role === 'parent') {
return ['小学阶段', '初中阶段', '高中阶段']
}
return []
})
// 密码强度检测
const passwordStrength = computed(() => {
const password = registerForm.password
if (!password) return { class: '', width: '0%', text: '' }
let score = 0
if (password.length >= 8) score++
if (/[a-z]/.test(password)) score++
if (/[A-Z]/.test(password)) score++
if (/[0-9]/.test(password)) score++
if (/[^a-zA-Z0-9]/.test(password)) score++
if (score <= 2) return { class: 'weak', width: '33%', text: '弱' }
if (score <= 3) return { class: 'medium', width: '66%', text: '中等' }
return { class: 'strong', width: '100%', text: '强' }
})
// 密码匹配检测
const passwordsMatch = computed(() => {
return registerForm.password === registerForm.confirmPassword
})
// 是否可以提交
const canSubmit = computed(() => {
return registerForm.name &&
registerForm.username &&
registerForm.email &&
registerForm.password &&
registerForm.confirmPassword &&
passwordsMatch.value &&
registerForm.role &&
registerForm.agreeTerms
})
// 注册处理
const handleRegister = async () => {
if (!canSubmit.value) return
isLoading.value = true
try {
// 模拟注册API调用
await new Promise(resolve => setTimeout(resolve, 2000))
// 注册成功,跳转到登录页
router.push('/login')
} catch (error) {
console.error('注册失败:', error)
} finally {
isLoading.value = false
}
}
// 浮动形状样式
const getShapeStyle = (index: number) => {
const shapes = ['🌟', '⭐', '✨', '💫', '🔥', '💎', '🎯', '🚀', '📚', '🎓']
const size = Math.random() * 25 + 15
const left = Math.random() * 100
const top = Math.random() * 100
const animationDelay = Math.random() * 8
const animationDuration = Math.random() * 4 + 6
return {
fontSize: `${size}px`,
left: `${left}%`,
top: `${top}%`,
animationDelay: `${animationDelay}s`,
animationDuration: `${animationDuration}s`,
'--content': `'${shapes[index % shapes.length]}'`
}
}
// 粒子样式
const getParticleStyle = (index: number) => {
const size = Math.random() * 4 + 2
const left = Math.random() * 100
const top = Math.random() * 100
const animationDelay = Math.random() * 5
const animationDuration = Math.random() * 3 + 3
return {
width: `${size}px`,
height: `${size}px`,
left: `${left}%`,
top: `${top}%`,
animationDelay: `${animationDelay}s`,
animationDuration: `${animationDuration}s`
}
}
</script>
<style scoped>
.register-container {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
position: relative;
overflow: hidden;
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.08;
animation: floatUp linear infinite;
}
.shape::before {
content: var(--content);
}
@keyframes floatUp {
0% {
transform: translateY(100vh) rotate(0deg);
opacity: 0;
}
10% {
opacity: 0.08;
}
90% {
opacity: 0.08;
}
100% {
transform: translateY(-100px) rotate(360deg);
opacity: 0;
}
}
.particle-system {
position: absolute;
width: 100%;
height: 100%;
}
.particle {
position: absolute;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
animation: particleFloat linear infinite;
}
@keyframes particleFloat {
0% {
transform: translateY(100vh);
opacity: 0;
}
10% {
opacity: 0.3;
}
90% {
opacity: 0.3;
}
100% {
transform: translateY(-50px);
opacity: 0;
}
}
.register-content {
position: relative;
z-index: 2;
display: grid;
grid-template-columns: 1fr 400px;
gap: 3rem;
max-width: 1200px;
margin: 0 auto;
}
.register-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: 2.5rem;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.header-section {
text-align: center;
margin-bottom: 2rem;
}
.logo-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.title {
font-size: 2.2rem;
font-weight: 700;
color: white;
margin: 0 0 0.5rem 0;
text-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.subtitle {
font-size: 1rem;
color: rgba(255, 255, 255, 0.8);
margin: 0;
}
.register-form {
display: flex;
flex-direction: column;
gap: 2rem;
}
.form-section {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.section-title {
font-size: 1.2rem;
font-weight: 600;
color: white;
margin: 0;
padding-bottom: 0.5rem;
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.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: 0.8rem 2.5rem 0.8rem 1rem;
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
background: rgba(255, 255, 255, 0.1);
color: white;
font-size: 0.9rem;
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 15px rgba(79, 172, 254, 0.3);
}
.form-select {
width: 100%;
padding: 0.8rem 1rem;
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 12px;
background: rgba(255, 255, 255, 0.1);
color: white;
font-size: 0.9rem;
transition: all 0.3s ease;
box-sizing: border-box;
}
.form-select:focus {
outline: none;
border-color: rgba(79, 172, 254, 0.8);
background: rgba(255, 255, 255, 0.15);
}
.form-select option {
background: #333;
color: white;
}
.input-icon {
position: absolute;
right: 1rem;
top: 50%;
transform: translateY(-50%);
font-size: 1rem;
opacity: 0.6;
}
.password-toggle {
position: absolute;
right: 1rem;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
font-size: 1rem;
cursor: pointer;
opacity: 0.6;
transition: opacity 0.3s ease;
}
.password-toggle:hover {
opacity: 1;
}
.password-strength {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: 0.5rem;
}
.strength-bar {
flex: 1;
height: 4px;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
overflow: hidden;
}
.strength-fill {
height: 100%;
transition: width 0.3s ease;
}
.strength-bar.weak .strength-fill {
background: #ff4757;
}
.strength-bar.medium .strength-fill {
background: #ffa502;
}
.strength-bar.strong .strength-fill {
background: #2ed573;
}
.strength-text {
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.7);
min-width: 30px;
}
.error-message {
font-size: 0.8rem;
color: #ff4757;
margin-top: 0.25rem;
}
.agreement-section {
padding: 1rem 0;
}
.checkbox-wrapper {
display: flex;
align-items: flex-start;
gap: 0.5rem;
cursor: pointer;
}
.checkbox {
width: 18px;
height: 18px;
margin-top: 2px;
accent-color: #4facfe;
}
.checkbox-label {
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.8);
line-height: 1.4;
}
.link {
color: #4facfe;
text-decoration: none;
transition: color 0.3s ease;
}
.link:hover {
color: #00f2fe;
}
.register-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;
}
.register-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(79, 172, 254, 0.4);
}
.register-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.loading-spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.login-section {
text-align: center;
margin-top: 1.5rem;
}
.login-text {
color: rgba(255, 255, 255, 0.7);
font-size: 0.9rem;
}
.login-link {
color: #4facfe;
text-decoration: none;
font-weight: 600;
margin-left: 0.5rem;
transition: color 0.3s ease;
}
.login-link:hover {
color: #00f2fe;
}
.benefits-section {
display: flex;
flex-direction: column;
justify-content: center;
}
.benefits-title {
font-size: 1.8rem;
font-weight: 700;
color: white;
margin-bottom: 2rem;
text-align: center;
}
.benefits-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
.benefit-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 16px;
padding: 1.5rem;
text-align: center;
transition: all 0.3s ease;
}
.benefit-card:hover {
transform: translateY(-5px);
background: rgba(255, 255, 255, 0.15);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.benefit-icon {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.benefit-title {
font-size: 1.1rem;
font-weight: 600;
color: white;
margin: 0 0 0.5rem 0;
}
.benefit-desc {
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.7);
margin: 0;
line-height: 1.4;
}
@media (max-width: 1024px) {
.register-content {
grid-template-columns: 1fr;
gap: 2rem;
}
.benefits-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.register-card {
padding: 2rem;
}
.form-row {
grid-template-columns: 1fr;
}
.title {
font-size: 1.8rem;
}
.benefits-title {
font-size: 1.5rem;
}
}
</style>