hookehuyr

feat(路由): 添加登录和注册页面路由

在路由配置中添加了登录和注册页面的路由,并删除了旧的AuthContext.jsx文件。同时,新增了LoginPage.vue和RegisterPage.vue页面组件,用于处理用户登录和注册功能。
import React, { createContext, useContext, useState, useEffect } from 'react';
/**
* Authentication Context for user login/logout functionality
*/
const AuthContext = createContext();
/**
* AuthProvider component to manage authentication state
*
* @param {Object} props - Component props
* @param {ReactNode} props.children - Child elements
* @returns {JSX.Element} AuthProvider component
*/
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [loading, setLoading] = useState(true);
// Check for saved user on mount
useEffect(() => {
const savedUser = localStorage.getItem('currentUser');
if (savedUser) {
setCurrentUser(JSON.parse(savedUser));
}
setLoading(false);
}, []);
// Login function
const login = (userData) => {
setCurrentUser(userData);
localStorage.setItem('currentUser', JSON.stringify(userData));
return true;
};
// Logout function
const logout = () => {
setCurrentUser(null);
localStorage.removeItem('currentUser');
};
// Context value
const value = {
currentUser,
loading,
login,
logout
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};
/**
* Hook to use auth functionality throughout the app
* @returns {Object} Auth context value
*/
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
};
export default AuthContext;
\ No newline at end of file
/*
* @Date: 2025-03-20 20:36:36
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-03-20 21:04:41
* @LastEditTime: 2025-03-20 21:16:30
* @FilePath: /mlaj/src/router/index.js
* @Description: 文件描述
*/
......@@ -32,6 +32,18 @@ const routes = [
component: () => import('../views/profile/ProfilePage.vue'),
meta: { title: 'Profile' },
},
{
path: '/login',
name: 'Login',
component: () => import('../views/auth/LoginPage.vue'),
meta: { title: 'Login' }
},
{
path: '/register',
name: 'Register',
component: () => import('../views/auth/RegisterPage.vue'),
meta: { title: 'Register' }
},
]
const router = createRouter({
......
<template>
<div class="min-h-screen flex flex-col bg-gradient-to-br from-green-50 via-teal-50 to-blue-50 py-12 px-4 sm:px-6 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<h1 class="text-center text-3xl font-bold text-gray-800 mb-2">亲子学院</h1>
<h2 class="text-center text-xl font-medium text-gray-600">欢迎回来</h2>
</div>
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<FrostedGlass class="py-8 px-6 rounded-lg">
<div v-if="error" class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-md">
{{ error }}
</div>
<form class="space-y-6" @submit.prevent="handleSubmit">
<div>
<label for="email" class="block text-sm font-medium text-gray-700">
邮箱 / 手机号
</label>
<input
id="email"
v-model="email"
name="email"
type="text"
autocomplete="email"
required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
/>
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700">
密码
</label>
<input
id="password"
v-model="password"
name="password"
type="password"
autocomplete="current-password"
required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
/>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<input
id="remember-me"
name="remember-me"
type="checkbox"
class="h-4 w-4 text-green-600 focus:ring-green-500 border-gray-300 rounded"
/>
<label for="remember-me" class="ml-2 block text-sm text-gray-700">
记住我
</label>
</div>
<div class="text-sm">
<a href="#" class="font-medium text-green-600 hover:text-green-500">
忘记密码?
</a>
</div>
</div>
<div>
<button
type="submit"
:disabled="loading"
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
:class="{ 'opacity-70 cursor-not-allowed': loading }"
>
{{ loading ? '登录中...' : '登 录' }}
</button>
</div>
</form>
<div class="mt-6">
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-white/30 backdrop-blur-sm text-gray-500">
或者
</span>
</div>
</div>
<div class="mt-6 grid grid-cols-2 gap-3">
<button
type="button"
class="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"
>
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-2.917 16.083c-2.258 0-4.083-1.825-4.083-4.083s1.825-4.083 4.083-4.083c1.103 0 2.024.402 2.735 1.067l-1.107 1.068c-.304-.292-.834-.63-1.628-.63-1.394 0-2.531 1.155-2.531 2.579 0 1.424 1.138 2.579 2.531 2.579 1.616 0 2.224-1.162 2.316-1.762h-2.316v-1.4h3.855c.036.204.064.408.064.677.001 2.332-1.563 3.988-3.919 3.988zm9.917-3.5h-1.75v1.75h-1.167v-1.75h-1.75v-1.166h1.75v-1.75h1.167v1.75h1.75v1.166z"></path>
</svg>
Google
</button>
<button
type="button"
class="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"
>
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-2.917 16.083c-2.258 0-4.083-1.825-4.083-4.083s1.825-4.083 4.083-4.083c1.103 0 2.024.402 2.735 1.067l-1.107 1.068c-.304-.292-.834-.63-1.628-.63-1.394 0-2.531 1.155-2.531 2.579 0 1.424 1.138 2.579 2.531 2.579 1.616 0 2.224-1.162 2.316-1.762h-2.316v-1.4h3.855c.036.204.064.408.064.677.001 2.332-1.563 3.988-3.919 3.988zm9.917-3.5h-1.75v1.75h-1.167v-1.75h-1.75v-1.166h1.75v-1.75h1.167v1.75h1.75v1.166z"></path>
</svg>
微信
</button>
</div>
</div>
<div class="text-center mt-6">
<p class="text-sm text-gray-600">
还没有账号?
<router-link to="/register" class="font-medium text-green-600 hover:text-green-500">
立即注册
</router-link>
</p>
</div>
</FrostedGlass>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import FrostedGlass from '@/components/ui/FrostedGlass.vue'
import { useAuth } from '@/contexts/auth'
const router = useRouter()
const { login } = useAuth()
const email = ref('')
const password = ref('')
const error = ref('')
const loading = ref(false)
const handleSubmit = async () => {
if (!email.value || !password.value) {
error.value = '请填写所有字段'
return
}
try {
error.value = ''
loading.value = true
// 使用auth.js中的login函数
const success = login({
email: email.value,
name: '李玉红',
avatar: 'https://cdn.ipadbiz.cn/mlaj/images/user-avatar-1.jpg'
})
if (success) {
router.push('/')
} else {
error.value = '登录失败,请检查您的凭据'
}
} catch (err) {
console.error('Login error:', err)
error.value = '登录时发生错误'
} finally {
loading.value = false
}
}
</script>
<template>
<div class="min-h-screen flex flex-col bg-gradient-to-br from-green-50 via-teal-50 to-blue-50 py-12 px-4 sm:px-6 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<h1 class="text-center text-3xl font-bold text-gray-800 mb-2">亲子学院</h1>
<h2 class="text-center text-xl font-medium text-gray-600">创建账号</h2>
</div>
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<FrostedGlass class="py-8 px-6 rounded-lg">
<div v-if="error" class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-md">
{{ error }}
</div>
<form class="space-y-6" @submit.prevent="handleSubmit">
<div>
<label for="name" class="block text-sm font-medium text-gray-700">
姓名 <span class="text-red-500">*</span>
</label>
<input
id="name"
v-model="formData.name"
type="text"
required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
/>
</div>
<div>
<label for="email" class="block text-sm font-medium text-gray-700">
邮箱 <span class="text-red-500">*</span>
</label>
<input
id="email"
v-model="formData.email"
type="email"
autocomplete="email"
required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
/>
</div>
<div>
<label for="phone" class="block text-sm font-medium text-gray-700">
手机号
</label>
<input
id="phone"
v-model="formData.phone"
type="tel"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
/>
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700">
密码 <span class="text-red-500">*</span>
</label>
<input
id="password"
v-model="formData.password"
type="password"
autocomplete="new-password"
required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
/>
</div>
<div>
<label for="confirmPassword" class="block text-sm font-medium text-gray-700">
确认密码 <span class="text-red-500">*</span>
</label>
<input
id="confirmPassword"
v-model="formData.confirmPassword"
type="password"
autocomplete="new-password"
required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
/>
</div>
<div class="flex items-center">
<input
id="agreeTerms"
v-model="formData.agreeTerms"
type="checkbox"
class="h-4 w-4 text-green-600 focus:ring-green-500 border-gray-300 rounded"
/>
<label for="agreeTerms" class="ml-2 block text-sm text-gray-700">
我已阅读并同意 <a href="#" class="text-green-600 hover:text-green-500">用户协议</a> 和 <a href="#" class="text-green-600 hover:text-green-500">隐私政策</a>
</label>
</div>
<div>
<button
type="submit"
:disabled="loading"
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
:class="{ 'opacity-70 cursor-not-allowed': loading }"
>
{{ loading ? '注册中...' : '立即注册' }}
</button>
</div>
</form>
<div class="mt-6">
<div class="relative">
<div class="absolute inset-0 flex items-center">
<div class="w-full border-t border-gray-300"></div>
</div>
<div class="relative flex justify-center text-sm">
<span class="px-2 bg-white/30 backdrop-blur-sm text-gray-500">
或者
</span>
</div>
</div>
<div class="mt-6 grid grid-cols-2 gap-3">
<button
type="button"
class="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"
>
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-2.917 16.083c-2.258 0-4.083-1.825-4.083-4.083s1.825-4.083 4.083-4.083c1.103 0 2.024.402 2.735 1.067l-1.107 1.068c-.304-.292-.834-.63-1.628-.63-1.394 0-2.531 1.155-2.531 2.579 0 1.424 1.138 2.579 2.531 2.579 1.616 0 2.224-1.162 2.316-1.762h-2.316v-1.4h3.855c.036.204.064.408.064.677.001 2.332-1.563 3.988-3.919 3.988zm9.917-3.5h-1.75v1.75h-1.167v-1.75h-1.75v-1.166h1.75v-1.75h1.167v1.75h1.75v1.166z"></path>
</svg>
Google
</button>
<button
type="button"
class="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm bg-white text-sm font-medium text-gray-700 hover:bg-gray-50"
>
<svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-2.917 16.083c-2.258 0-4.083-1.825-4.083-4.083s1.825-4.083 4.083-4.083c1.103 0 2.024.402 2.735 1.067l-1.107 1.068c-.304-.292-.834-.63-1.628-.63-1.394 0-2.531 1.155-2.531 2.579 0 1.424 1.138 2.579 2.531 2.579 1.616 0 2.224-1.162 2.316-1.762h-2.316v-1.4h3.855c.036.204.064.408.064.677.001 2.332-1.563 3.988-3.919 3.988zm9.917-3.5h-1.75v1.75h-1.167v-1.75h-1.75v-1.166h1.75v-1.75h1.167v1.75h1.75v1.166z"></path>
</svg>
微信
</button>
</div>
</div>
<div class="text-center mt-6">
<p class="text-sm text-gray-600">
已有账号?
<router-link to="/login" class="font-medium text-green-600 hover:text-green-500">
登录
</router-link>
</p>
</div>
</FrostedGlass>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import FrostedGlass from '@/components/ui/FrostedGlass.vue'
import { useAuth } from '@/contexts/auth'
const router = useRouter()
const { login } = useAuth()
const formData = reactive({
name: '',
email: '',
phone: '',
password: '',
confirmPassword: '',
agreeTerms: false
})
const error = ref('')
const loading = ref(false)
const handleSubmit = async () => {
if (!formData.name || !formData.email || !formData.password) {
error.value = '请填写所有必填字段'
return
}
if (formData.password !== formData.confirmPassword) {
error.value = '两次输入的密码不一致'
return
}
if (!formData.agreeTerms) {
error.value = '请阅读并同意用户协议'
return
}
try {
error.value = ''
loading.value = true
// 使用auth.js中的login函数
const success = login({
email: formData.email,
name: formData.name,
avatar: 'https://cdn.ipadbiz.cn/mlaj/images/user-avatar-3.jpg'
})
if (success) {
router.push('/')
} else {
error.value = '注册失败,请稍后再试'
}
} catch (err) {
console.error('Registration error:', err)
error.value = '注册时发生错误'
} finally {
loading.value = false
}
}
</script>