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

803 lines
19 KiB
Vue
Raw Normal View History

2025-08-03 17:20:28 +08:00
<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>