hookehuyr

feat(登录): 添加忘记密码功能

在登录页面中,将忘记密码的链接替换为`<router-link>`,并添加忘记密码页面及其路由配置。用户现在可以通过该页面重置密码。
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 22:46:37 4 + * @LastEditTime: 2025-03-21 10:27:02
5 * @FilePath: /mlaj/src/router/index.js 5 * @FilePath: /mlaj/src/router/index.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
...@@ -45,6 +45,12 @@ const routes = [ ...@@ -45,6 +45,12 @@ const routes = [
45 meta: { title: '注册' } 45 meta: { title: '注册' }
46 }, 46 },
47 { 47 {
48 + path: '/forgotPwd',
49 + name: 'ForgotPassword',
50 + component: () => import('../views/auth/ForgotPasswordPage.vue'),
51 + meta: { title: '忘记密码' }
52 + },
53 + {
48 path: '/activities', 54 path: '/activities',
49 name: 'Activities', 55 name: 'Activities',
50 component: () => import('../views/activities/ActivitiesPage.vue'), 56 component: () => import('../views/activities/ActivitiesPage.vue'),
......
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="phone" class="block text-sm font-medium text-gray-700">
17 + 手机号 <span class="text-red-500">*</span>
18 + </label>
19 + <input
20 + id="phone"
21 + v-model="formData.phone"
22 + type="tel"
23 + required
24 + pattern="^1[3-9]\d{9}$"
25 + 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"
26 + />
27 + </div>
28 +
29 + <div>
30 + <label for="verificationCode" class="block text-sm font-medium text-gray-700">
31 + 验证码 <span class="text-red-500">*</span>
32 + </label>
33 + <div class="flex space-x-2">
34 + <input
35 + id="verificationCode"
36 + v-model="formData.verificationCode"
37 + type="text"
38 + required
39 + maxlength="6"
40 + 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"
41 + />
42 + <button
43 + type="button"
44 + :disabled="countdown > 0"
45 + @click="sendVerificationCode"
46 + class="mt-1 px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap"
47 + >
48 + {{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }}
49 + </button>
50 + </div>
51 + </div>
52 +
53 + <div>
54 + <label for="password" class="block text-sm font-medium text-gray-700">
55 + 新密码 <span class="text-red-500">*</span>
56 + </label>
57 + <input
58 + id="password"
59 + v-model="formData.password"
60 + type="password"
61 + required
62 + minlength="6"
63 + 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"
64 + />
65 + </div>
66 +
67 + <div>
68 + <label for="confirmPassword" class="block text-sm font-medium text-gray-700">
69 + 确认密码 <span class="text-red-500">*</span>
70 + </label>
71 + <input
72 + id="confirmPassword"
73 + v-model="formData.confirmPassword"
74 + type="password"
75 + required
76 + minlength="6"
77 + 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"
78 + />
79 + </div>
80 +
81 + <div>
82 + <button
83 + type="submit"
84 + :disabled="loading"
85 + 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"
86 + :class="{ 'opacity-70 cursor-not-allowed': loading }"
87 + >
88 + {{ loading ? '提交中...' : '重置密码' }}
89 + </button>
90 + </div>
91 + </form>
92 +
93 + <div class="text-center mt-6">
94 + <p class="text-sm text-gray-600">
95 + 记起密码了?
96 + <router-link to="/login" class="font-medium text-green-600 hover:text-green-500">
97 + 返回登录
98 + </router-link>
99 + </p>
100 + </div>
101 + </FrostedGlass>
102 + </div>
103 + </div>
104 +</template>
105 +
106 +<script setup>
107 +import { ref, reactive } from 'vue'
108 +import { useRouter } from 'vue-router'
109 +import FrostedGlass from '@/components/ui/FrostedGlass.vue'
110 +
111 +const router = useRouter()
112 +const error = ref('')
113 +const loading = ref(false)
114 +const countdown = ref(0)
115 +
116 +const formData = reactive({
117 + phone: '',
118 + verificationCode: '',
119 + password: '',
120 + confirmPassword: ''
121 +})
122 +
123 +const startCountdown = () => {
124 + countdown.value = 60
125 + const timer = setInterval(() => {
126 + countdown.value--
127 + if (countdown.value <= 0) {
128 + clearInterval(timer)
129 + }
130 + }, 1000)
131 +}
132 +
133 +const sendVerificationCode = async () => {
134 + if (!/^1[3-9]\d{9}$/.test(formData.phone)) {
135 + error.value = '请输入正确的手机号'
136 + return
137 + }
138 +
139 + try {
140 + // TODO: 调用发送验证码API
141 + startCountdown()
142 + } catch (err) {
143 + console.error('Send verification code error:', err)
144 + error.value = '发送验证码失败,请稍后重试'
145 + }
146 +}
147 +
148 +const handleSubmit = async () => {
149 + if (formData.password !== formData.confirmPassword) {
150 + error.value = '两次输入的密码不一致'
151 + return
152 + }
153 +
154 + try {
155 + error.value = ''
156 + loading.value = true
157 +
158 + // TODO: 调用重置密码API
159 +
160 + // 重置成功后跳转到登录页
161 + router.push('/login')
162 + } catch (err) {
163 + console.error('Reset password error:', err)
164 + error.value = '重置密码失败,请稍后重试'
165 + } finally {
166 + loading.value = false
167 + }
168 +}
169 +</script>
...@@ -56,9 +56,9 @@ ...@@ -56,9 +56,9 @@
56 </div> 56 </div>
57 57
58 <div class="text-sm"> 58 <div class="text-sm">
59 - <a href="#" class="font-medium text-green-600 hover:text-green-500"> 59 + <router-link to="/forgotPwd" class="font-medium text-green-600 hover:text-green-500">
60 忘记密码? 60 忘记密码?
61 - </a> 61 + </router-link>
62 </div> 62 </div>
63 </div> 63 </div>
64 64
......