hookehuyr

feat(注册页面): 添加手机号验证和验证码功能

增加手机号输入字段的验证逻辑,确保输入格式正确。添加验证码输入字段和获取验证码按钮,并实现倒计时功能。优化表单提交逻辑,确保所有必填字段都已填写且手机号格式正确。
...@@ -27,15 +27,44 @@ ...@@ -27,15 +27,44 @@
27 27
28 <div> 28 <div>
29 <label for="phone" class="block text-sm font-medium text-gray-700"> 29 <label for="phone" class="block text-sm font-medium text-gray-700">
30 - 手机号 30 + 手机号 <span class="text-red-500">*</span>
31 </label> 31 </label>
32 <input 32 <input
33 id="phone" 33 id="phone"
34 v-model="formData.phone" 34 v-model="formData.phone"
35 autocomplete="tel" 35 autocomplete="tel"
36 type="tel" 36 type="tel"
37 + required
38 + pattern="^1[3-9]\d{9}$"
39 + @blur="validatePhone"
37 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" 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 + :class="{ 'border-red-300': phoneError }"
38 /> 42 />
43 + <p v-if="phoneError" class="mt-1 text-sm text-red-600">{{ phoneError }}</p>
44 + </div>
45 +
46 + <div>
47 + <label for="verificationCode" class="block text-sm font-medium text-gray-700">
48 + 验证码 <span class="text-red-500">*</span>
49 + </label>
50 + <div class="flex space-x-2">
51 + <input
52 + id="verificationCode"
53 + v-model="formData.verificationCode"
54 + type="text"
55 + required
56 + maxlength="6"
57 + 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"
58 + />
59 + <button
60 + type="button"
61 + :disabled="countdown > 0 || !isPhoneValid"
62 + @click="sendVerificationCode"
63 + 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"
64 + >
65 + {{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }}
66 + </button>
67 + </div>
39 </div> 68 </div>
40 69
41 <div> 70 <div>
...@@ -161,39 +190,72 @@ useTitle($route.meta.title); ...@@ -161,39 +190,72 @@ useTitle($route.meta.title);
161 const router = useRouter() 190 const router = useRouter()
162 const { login } = useAuth() 191 const { login } = useAuth()
163 192
193 +const countdown = ref(0)
194 +
164 const formData = reactive({ 195 const formData = reactive({
165 name: '', 196 name: '',
166 phone: '', 197 phone: '',
198 + verificationCode: '',
167 password: '', 199 password: '',
168 confirmPassword: '', 200 confirmPassword: '',
169 agreeTerms: false 201 agreeTerms: false
170 }) 202 })
171 203
172 -const error = ref('') 204 +const startCountdown = () => {
173 -const loading = ref(false) 205 + countdown.value = 60
174 -const showTerms = ref(false) 206 + const timer = setInterval(() => {
175 -const showPrivacy = ref(false) 207 + countdown.value--
176 -const popupTitle = ref('') 208 + if (countdown.value <= 0) {
177 -const popupType = ref('') 209 + clearInterval(timer)
178 - 210 + }
179 -const openTerms = () => { 211 + }, 1000)
180 - popupTitle.value = '用户协议' 212 +}
181 - popupType.value = 'terms' 213 +
182 - showTerms.value = true 214 +const phoneError = ref('')
215 +const isPhoneValid = ref(false)
216 +
217 +const validatePhone = () => {
218 + if (!formData.phone) {
219 + phoneError.value = '请输入手机号'
220 + isPhoneValid.value = false
221 + return
222 + }
223 +
224 + if (!/^1[3-9]\d{9}$/.test(formData.phone)) {
225 + phoneError.value = '请输入正确的手机号'
226 + isPhoneValid.value = false
227 + return
228 + }
229 +
230 + phoneError.value = ''
231 + isPhoneValid.value = true
183 } 232 }
184 233
185 -const openPrivacy = () => { 234 +const sendVerificationCode = async () => {
186 - popupTitle.value = '隐私政策' 235 + if (!isPhoneValid.value) {
187 - popupType.value = 'privacy' 236 + return
188 - showPrivacy.value = true 237 + }
238 +
239 + try {
240 + // TODO: 调用发送验证码API
241 + startCountdown()
242 + } catch (err) {
243 + console.error('Send verification code error:', err)
244 + error.value = '发送验证码失败,请稍后重试'
245 + }
189 } 246 }
190 247
191 const handleSubmit = async () => { 248 const handleSubmit = async () => {
192 - if (!formData.name || !formData.password) { 249 + if (!formData.name || !formData.password || !formData.phone || !formData.verificationCode) {
193 error.value = '请填写所有必填字段' 250 error.value = '请填写所有必填字段'
194 return 251 return
195 } 252 }
196 253
254 + if (!/^1[3-9]\d{9}$/.test(formData.phone)) {
255 + error.value = '请输入正确的手机号'
256 + return
257 + }
258 +
197 if (formData.password !== formData.confirmPassword) { 259 if (formData.password !== formData.confirmPassword) {
198 error.value = '两次输入的密码不一致' 260 error.value = '两次输入的密码不一致'
199 return 261 return
......