hookehuyr

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

在路由配置中添加了登录和注册页面的路由,并删除了旧的AuthContext.jsx文件。同时,新增了LoginPage.vue和RegisterPage.vue页面组件,用于处理用户登录和注册功能。
1 -import React, { createContext, useContext, useState, useEffect } from 'react';
2 -
3 -/**
4 - * Authentication Context for user login/logout functionality
5 - */
6 -const AuthContext = createContext();
7 -
8 -/**
9 - * AuthProvider component to manage authentication state
10 - *
11 - * @param {Object} props - Component props
12 - * @param {ReactNode} props.children - Child elements
13 - * @returns {JSX.Element} AuthProvider component
14 - */
15 -export const AuthProvider = ({ children }) => {
16 - const [currentUser, setCurrentUser] = useState(null);
17 - const [loading, setLoading] = useState(true);
18 -
19 - // Check for saved user on mount
20 - useEffect(() => {
21 - const savedUser = localStorage.getItem('currentUser');
22 - if (savedUser) {
23 - setCurrentUser(JSON.parse(savedUser));
24 - }
25 - setLoading(false);
26 - }, []);
27 -
28 - // Login function
29 - const login = (userData) => {
30 - setCurrentUser(userData);
31 - localStorage.setItem('currentUser', JSON.stringify(userData));
32 - return true;
33 - };
34 -
35 - // Logout function
36 - const logout = () => {
37 - setCurrentUser(null);
38 - localStorage.removeItem('currentUser');
39 - };
40 -
41 - // Context value
42 - const value = {
43 - currentUser,
44 - loading,
45 - login,
46 - logout
47 - };
48 -
49 - return (
50 - <AuthContext.Provider value={value}>
51 - {!loading && children}
52 - </AuthContext.Provider>
53 - );
54 -};
55 -
56 -/**
57 - * Hook to use auth functionality throughout the app
58 - * @returns {Object} Auth context value
59 - */
60 -export const useAuth = () => {
61 - const context = useContext(AuthContext);
62 - if (!context) {
63 - throw new Error("useAuth must be used within an AuthProvider");
64 - }
65 - return context;
66 -};
67 -
68 -export default AuthContext;
...\ No newline at end of file ...\ No newline at end of file
1 /* 1 /*
2 * @Date: 2025-03-20 20:36:36 2 * @Date: 2025-03-20 20:36:36
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-03-20 21:04:41 4 + * @LastEditTime: 2025-03-20 21:16:30
5 * @FilePath: /mlaj/src/router/index.js 5 * @FilePath: /mlaj/src/router/index.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
...@@ -32,6 +32,18 @@ const routes = [ ...@@ -32,6 +32,18 @@ const routes = [
32 component: () => import('../views/profile/ProfilePage.vue'), 32 component: () => import('../views/profile/ProfilePage.vue'),
33 meta: { title: 'Profile' }, 33 meta: { title: 'Profile' },
34 }, 34 },
35 + {
36 + path: '/login',
37 + name: 'Login',
38 + component: () => import('../views/auth/LoginPage.vue'),
39 + meta: { title: 'Login' }
40 + },
41 + {
42 + path: '/register',
43 + name: 'Register',
44 + component: () => import('../views/auth/RegisterPage.vue'),
45 + meta: { title: 'Register' }
46 + },
35 ] 47 ]
36 48
37 const router = createRouter({ 49 const router = createRouter({
......
1 +<template>
2 + <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">
3 + <div class="sm:mx-auto sm:w-full sm:max-w-md">
4 + <h1 class="text-center text-3xl font-bold text-gray-800 mb-2">亲子学院</h1>
5 + <h2 class="text-center text-xl font-medium text-gray-600">欢迎回来</h2>
6 + </div>
7 +
8 + <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
9 + <FrostedGlass class="py-8 px-6 rounded-lg">
10 + <div v-if="error" class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-md">
11 + {{ error }}
12 + </div>
13 +
14 + <form class="space-y-6" @submit.prevent="handleSubmit">
15 + <div>
16 + <label for="email" class="block text-sm font-medium text-gray-700">
17 + 邮箱 / 手机号
18 + </label>
19 + <input
20 + id="email"
21 + v-model="email"
22 + name="email"
23 + type="text"
24 + autocomplete="email"
25 + required
26 + 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"
27 + />
28 + </div>
29 +
30 + <div>
31 + <label for="password" class="block text-sm font-medium text-gray-700">
32 + 密码
33 + </label>
34 + <input
35 + id="password"
36 + v-model="password"
37 + name="password"
38 + type="password"
39 + autocomplete="current-password"
40 + required
41 + 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"
42 + />
43 + </div>
44 +
45 + <div class="flex items-center justify-between">
46 + <div class="flex items-center">
47 + <input
48 + id="remember-me"
49 + name="remember-me"
50 + type="checkbox"
51 + class="h-4 w-4 text-green-600 focus:ring-green-500 border-gray-300 rounded"
52 + />
53 + <label for="remember-me" class="ml-2 block text-sm text-gray-700">
54 + 记住我
55 + </label>
56 + </div>
57 +
58 + <div class="text-sm">
59 + <a href="#" class="font-medium text-green-600 hover:text-green-500">
60 + 忘记密码?
61 + </a>
62 + </div>
63 + </div>
64 +
65 + <div>
66 + <button
67 + type="submit"
68 + :disabled="loading"
69 + 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"
70 + :class="{ 'opacity-70 cursor-not-allowed': loading }"
71 + >
72 + {{ loading ? '登录中...' : '登 录' }}
73 + </button>
74 + </div>
75 + </form>
76 +
77 + <div class="mt-6">
78 + <div class="relative">
79 + <div class="absolute inset-0 flex items-center">
80 + <div class="w-full border-t border-gray-300"></div>
81 + </div>
82 + <div class="relative flex justify-center text-sm">
83 + <span class="px-2 bg-white/30 backdrop-blur-sm text-gray-500">
84 + 或者
85 + </span>
86 + </div>
87 + </div>
88 +
89 + <div class="mt-6 grid grid-cols-2 gap-3">
90 + <button
91 + type="button"
92 + 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"
93 + >
94 + <svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
95 + <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>
96 + </svg>
97 + Google
98 + </button>
99 + <button
100 + type="button"
101 + 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"
102 + >
103 + <svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
104 + <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>
105 + </svg>
106 + 微信
107 + </button>
108 + </div>
109 + </div>
110 +
111 + <div class="text-center mt-6">
112 + <p class="text-sm text-gray-600">
113 + 还没有账号?
114 + <router-link to="/register" class="font-medium text-green-600 hover:text-green-500">
115 + 立即注册
116 + </router-link>
117 + </p>
118 + </div>
119 + </FrostedGlass>
120 + </div>
121 + </div>
122 +</template>
123 +
124 +<script setup>
125 +import { ref } from 'vue'
126 +import { useRouter } from 'vue-router'
127 +import FrostedGlass from '@/components/ui/FrostedGlass.vue'
128 +import { useAuth } from '@/contexts/auth'
129 +
130 +const router = useRouter()
131 +const { login } = useAuth()
132 +
133 +const email = ref('')
134 +const password = ref('')
135 +const error = ref('')
136 +const loading = ref(false)
137 +
138 +const handleSubmit = async () => {
139 + if (!email.value || !password.value) {
140 + error.value = '请填写所有字段'
141 + return
142 + }
143 +
144 + try {
145 + error.value = ''
146 + loading.value = true
147 +
148 + // 使用auth.js中的login函数
149 + const success = login({
150 + email: email.value,
151 + name: '李玉红',
152 + avatar: 'https://cdn.ipadbiz.cn/mlaj/images/user-avatar-1.jpg'
153 + })
154 +
155 + if (success) {
156 + router.push('/')
157 + } else {
158 + error.value = '登录失败,请检查您的凭据'
159 + }
160 + } catch (err) {
161 + console.error('Login error:', err)
162 + error.value = '登录时发生错误'
163 + } finally {
164 + loading.value = false
165 + }
166 +}
167 +</script>
1 +<template>
2 + <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">
3 + <div class="sm:mx-auto sm:w-full sm:max-w-md">
4 + <h1 class="text-center text-3xl font-bold text-gray-800 mb-2">亲子学院</h1>
5 + <h2 class="text-center text-xl font-medium text-gray-600">创建账号</h2>
6 + </div>
7 +
8 + <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
9 + <FrostedGlass class="py-8 px-6 rounded-lg">
10 + <div v-if="error" class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-md">
11 + {{ error }}
12 + </div>
13 +
14 + <form class="space-y-6" @submit.prevent="handleSubmit">
15 + <div>
16 + <label for="name" class="block text-sm font-medium text-gray-700">
17 + 姓名 <span class="text-red-500">*</span>
18 + </label>
19 + <input
20 + id="name"
21 + v-model="formData.name"
22 + type="text"
23 + required
24 + 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"
25 + />
26 + </div>
27 +
28 + <div>
29 + <label for="email" class="block text-sm font-medium text-gray-700">
30 + 邮箱 <span class="text-red-500">*</span>
31 + </label>
32 + <input
33 + id="email"
34 + v-model="formData.email"
35 + type="email"
36 + autocomplete="email"
37 + required
38 + 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"
39 + />
40 + </div>
41 +
42 + <div>
43 + <label for="phone" class="block text-sm font-medium text-gray-700">
44 + 手机号
45 + </label>
46 + <input
47 + id="phone"
48 + v-model="formData.phone"
49 + type="tel"
50 + 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"
51 + />
52 + </div>
53 +
54 + <div>
55 + <label for="password" class="block text-sm font-medium text-gray-700">
56 + 密码 <span class="text-red-500">*</span>
57 + </label>
58 + <input
59 + id="password"
60 + v-model="formData.password"
61 + type="password"
62 + autocomplete="new-password"
63 + required
64 + 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"
65 + />
66 + </div>
67 +
68 + <div>
69 + <label for="confirmPassword" class="block text-sm font-medium text-gray-700">
70 + 确认密码 <span class="text-red-500">*</span>
71 + </label>
72 + <input
73 + id="confirmPassword"
74 + v-model="formData.confirmPassword"
75 + type="password"
76 + autocomplete="new-password"
77 + required
78 + 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"
79 + />
80 + </div>
81 +
82 + <div class="flex items-center">
83 + <input
84 + id="agreeTerms"
85 + v-model="formData.agreeTerms"
86 + type="checkbox"
87 + class="h-4 w-4 text-green-600 focus:ring-green-500 border-gray-300 rounded"
88 + />
89 + <label for="agreeTerms" class="ml-2 block text-sm text-gray-700">
90 + 我已阅读并同意 <a href="#" class="text-green-600 hover:text-green-500">用户协议</a> 和 <a href="#" class="text-green-600 hover:text-green-500">隐私政策</a>
91 + </label>
92 + </div>
93 +
94 + <div>
95 + <button
96 + type="submit"
97 + :disabled="loading"
98 + 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"
99 + :class="{ 'opacity-70 cursor-not-allowed': loading }"
100 + >
101 + {{ loading ? '注册中...' : '立即注册' }}
102 + </button>
103 + </div>
104 + </form>
105 +
106 + <div class="mt-6">
107 + <div class="relative">
108 + <div class="absolute inset-0 flex items-center">
109 + <div class="w-full border-t border-gray-300"></div>
110 + </div>
111 + <div class="relative flex justify-center text-sm">
112 + <span class="px-2 bg-white/30 backdrop-blur-sm text-gray-500">
113 + 或者
114 + </span>
115 + </div>
116 + </div>
117 +
118 + <div class="mt-6 grid grid-cols-2 gap-3">
119 + <button
120 + type="button"
121 + 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"
122 + >
123 + <svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
124 + <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>
125 + </svg>
126 + Google
127 + </button>
128 + <button
129 + type="button"
130 + 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"
131 + >
132 + <svg class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 24 24">
133 + <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>
134 + </svg>
135 + 微信
136 + </button>
137 + </div>
138 + </div>
139 +
140 + <div class="text-center mt-6">
141 + <p class="text-sm text-gray-600">
142 + 已有账号?
143 + <router-link to="/login" class="font-medium text-green-600 hover:text-green-500">
144 + 登录
145 + </router-link>
146 + </p>
147 + </div>
148 + </FrostedGlass>
149 + </div>
150 + </div>
151 +</template>
152 +
153 +<script setup>
154 +import { ref, reactive } from 'vue'
155 +import { useRouter } from 'vue-router'
156 +import FrostedGlass from '@/components/ui/FrostedGlass.vue'
157 +import { useAuth } from '@/contexts/auth'
158 +
159 +const router = useRouter()
160 +const { login } = useAuth()
161 +
162 +const formData = reactive({
163 + name: '',
164 + email: '',
165 + phone: '',
166 + password: '',
167 + confirmPassword: '',
168 + agreeTerms: false
169 +})
170 +
171 +const error = ref('')
172 +const loading = ref(false)
173 +
174 +const handleSubmit = async () => {
175 + if (!formData.name || !formData.email || !formData.password) {
176 + error.value = '请填写所有必填字段'
177 + return
178 + }
179 +
180 + if (formData.password !== formData.confirmPassword) {
181 + error.value = '两次输入的密码不一致'
182 + return
183 + }
184 +
185 + if (!formData.agreeTerms) {
186 + error.value = '请阅读并同意用户协议'
187 + return
188 + }
189 +
190 + try {
191 + error.value = ''
192 + loading.value = true
193 +
194 + // 使用auth.js中的login函数
195 + const success = login({
196 + email: formData.email,
197 + name: formData.name,
198 + avatar: 'https://cdn.ipadbiz.cn/mlaj/images/user-avatar-3.jpg'
199 + })
200 +
201 + if (success) {
202 + router.push('/')
203 + } else {
204 + error.value = '注册失败,请稍后再试'
205 + }
206 + } catch (err) {
207 + console.error('Registration error:', err)
208 + error.value = '注册时发生错误'
209 + } finally {
210 + loading.value = false
211 + }
212 +}
213 +</script>