hookehuyr

fix(登录页): 修正验证码校验规则为4位数字

修复登录页验证码校验逻辑,将验证码长度从4-6位改为严格4位数字
1 <!-- 1 <!--
2 * @Date: 2025-11-10 18:08:59 2 * @Date: 2025-11-10 18:08:59
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-11-11 14:01:40 4 + * @LastEditTime: 2025-11-11 15:51:10
5 * @FilePath: /stdj_h5/src/views/Login.vue 5 * @FilePath: /stdj_h5/src/views/Login.vue
6 * @Description: 登录页 6 * @Description: 登录页
7 --> 7 -->
8 <template> 8 <template>
9 - <div class="login-page"> 9 + <div class="login-page">
10 - <!-- 顶部LOGO标题 --> 10 + <!-- 顶部LOGO标题 -->
11 - <div class="logo-title"> 11 + <div class="logo-title">
12 - <img class="logo-img" src="https://cdn.ipadbiz.cn/stdj/images/logo@2x.png" alt="Logo"> 12 + <img class="logo-img" src="https://cdn.ipadbiz.cn/stdj/images/logo@2x.png" alt="Logo">
13 + </div>
14 +
15 + <!-- 戒子身份验证容器 -->
16 + <div class="auth-card">
17 + <div class="card-title">戒子身份验证</div>
18 +
19 + <!-- 登录表单:手机号 -->
20 + <div class="form-item">
21 + <div class="input-with-icon phone">
22 + <input class="input" type="tel" placeholder="请输入手机号" v-model="phone" maxlength="11" />
13 </div> 23 </div>
24 + </div>
14 25
15 - <!-- 戒子身份验证容器 --> 26 + <!-- 登录表单:验证码 + 获取验证码 -->
16 - <div class="auth-card"> 27 + <div class="form-item">
17 - <div class="card-title">戒子身份验证</div> 28 + <div class="code-row">
18 - 29 + <div class="input-with-icon code">
19 - <!-- 登录表单:手机号 --> 30 + <input class="input" type="tel" placeholder="请输入验证码" v-model="code" maxlength="6" />
20 - <div class="form-item"> 31 + </div>
21 - <div class="input-with-icon phone"> 32 + <div class="btn send" :class="{ disabled: send_disabled }" @click="on_click_send_sms">
22 - <input class="input" type="tel" placeholder="请输入手机号" v-model="phone" maxlength="11" /> 33 + <span v-if="countdown === 0">获取验证码</span>
23 - </div> 34 + <span v-else>{{ countdown }}s后重试</span>
24 - </div> 35 + </div>
25 -
26 - <!-- 登录表单:验证码 + 获取验证码 -->
27 - <div class="form-item">
28 - <div class="code-row">
29 - <div class="input-with-icon code">
30 - <input class="input" type="tel" placeholder="请输入验证码" v-model="code" maxlength="6" />
31 - </div>
32 - <div class="btn send" :class="{ disabled: send_disabled }" @click="on_click_send_sms">
33 - <span v-if="countdown === 0">获取验证码</span>
34 - <span v-else>{{ countdown }}s后重试</span>
35 - </div>
36 - </div>
37 - </div>
38 -
39 - <!-- 立即验证按钮 -->
40 - <div class="form-item">
41 - <div class="btn primary" :class="{ disabled: login_disabled }" @click="on_click_login">立即验证</div>
42 - </div>
43 </div> 36 </div>
37 + </div>
38 +
39 + <!-- 立即验证按钮 -->
40 + <div class="form-item">
41 + <div class="btn primary" :class="{ disabled: login_disabled }" @click="on_click_login">立即验证</div>
42 + </div>
44 </div> 43 </div>
44 + </div>
45 </template> 45 </template>
46 46
47 <script setup> 47 <script setup>
48 -import { ref, onUnmounted, computed } from 'vue' 48 +import { ref, onUnmounted, computed, onMounted } from 'vue'
49 import { useRoute, useRouter } from 'vue-router' 49 import { useRoute, useRouter } from 'vue-router'
50 import { useTitle } from '@vueuse/core' 50 import { useTitle } from '@vueuse/core'
51 import { smsAPI } from '@/api/common.js' 51 import { smsAPI } from '@/api/common.js'
...@@ -71,7 +71,7 @@ const logging = ref(false) ...@@ -71,7 +71,7 @@ const logging = ref(false)
71 * 说明:倒计时进行中或正在发送或手机号无效时不可操作 71 * 说明:倒计时进行中或正在发送或手机号无效时不可操作
72 */ 72 */
73 const send_disabled = computed(function () { 73 const send_disabled = computed(function () {
74 - return countdown.value > 0 || sending.value || !is_valid_phone(phone.value) 74 + return countdown.value > 0 || sending.value || !is_valid_phone(phone.value)
75 }) 75 })
76 76
77 /** 77 /**
...@@ -79,11 +79,11 @@ const send_disabled = computed(function () { ...@@ -79,11 +79,11 @@ const send_disabled = computed(function () {
79 * 说明:登录进行中或手机号/验证码未通过校验时不可操作 79 * 说明:登录进行中或手机号/验证码未通过校验时不可操作
80 */ 80 */
81 const login_disabled = computed(function () { 81 const login_disabled = computed(function () {
82 - return ( 82 + return (
83 - logging.value || 83 + logging.value ||
84 - !is_valid_phone(phone.value) || 84 + !is_valid_phone(phone.value) ||
85 - !/^\d{4,6}$/.test(String(code.value || '').trim()) 85 + !/^\d{4}$/.test(String(code.value || '').trim())
86 - ) 86 + )
87 }) 87 })
88 88
89 /** 89 /**
...@@ -92,8 +92,8 @@ const login_disabled = computed(function () { ...@@ -92,8 +92,8 @@ const login_disabled = computed(function () {
92 * @returns {void} 92 * @returns {void}
93 */ 93 */
94 const on_click_send_sms = function () { 94 const on_click_send_sms = function () {
95 - if (send_disabled.value) return 95 + if (send_disabled.value) return
96 - on_send_sms() 96 + on_send_sms()
97 } 97 }
98 98
99 /** 99 /**
...@@ -102,8 +102,8 @@ const on_click_send_sms = function () { ...@@ -102,8 +102,8 @@ const on_click_send_sms = function () {
102 * @returns {void} 102 * @returns {void}
103 */ 103 */
104 const on_click_login = function () { 104 const on_click_login = function () {
105 - if (login_disabled.value) return 105 + if (login_disabled.value) return
106 - on_login() 106 + on_login()
107 } 107 }
108 108
109 /** 109 /**
...@@ -113,7 +113,7 @@ const on_click_login = function () { ...@@ -113,7 +113,7 @@ const on_click_login = function () {
113 * @returns {boolean} 是否有效 113 * @returns {boolean} 是否有效
114 */ 114 */
115 const is_valid_phone = function (v) { 115 const is_valid_phone = function (v) {
116 - return /^1\d{10}$/.test(String(v || '').trim()) 116 + return /^1\d{10}$/.test(String(v || '').trim())
117 } 117 }
118 118
119 /** 119 /**
...@@ -122,19 +122,19 @@ const is_valid_phone = function (v) { ...@@ -122,19 +122,19 @@ const is_valid_phone = function (v) {
122 * @returns {void} 122 * @returns {void}
123 */ 123 */
124 const start_countdown = function () { 124 const start_countdown = function () {
125 - countdown.value = 60 125 + countdown.value = 60
126 - if (timer_id.value) { 126 + if (timer_id.value) {
127 - clearInterval(timer_id.value) 127 + clearInterval(timer_id.value)
128 - timer_id.value = null 128 + timer_id.value = null
129 + }
130 + timer_id.value = setInterval(function () {
131 + countdown.value = countdown.value - 1
132 + if (countdown.value <= 0) {
133 + clearInterval(timer_id.value)
134 + timer_id.value = null
135 + countdown.value = 0
129 } 136 }
130 - timer_id.value = setInterval(function () { 137 + }, 1000)
131 - countdown.value = countdown.value - 1
132 - if (countdown.value <= 0) {
133 - clearInterval(timer_id.value)
134 - timer_id.value = null
135 - countdown.value = 0
136 - }
137 - }, 1000)
138 } 138 }
139 139
140 /** 140 /**
...@@ -143,23 +143,23 @@ const start_countdown = function () { ...@@ -143,23 +143,23 @@ const start_countdown = function () {
143 * @returns {Promise<void>} 143 * @returns {Promise<void>}
144 */ 144 */
145 const on_send_sms = async function () { 145 const on_send_sms = async function () {
146 - if (sending.value || countdown.value > 0) return 146 + if (sending.value || countdown.value > 0) return
147 - if (!is_valid_phone(phone.value)) { 147 + if (!is_valid_phone(phone.value)) {
148 - showFailToast('请输入有效的手机号') 148 + showFailToast('请输入有效的手机号')
149 - return 149 + return
150 - } 150 + }
151 - try { 151 + try {
152 - sending.value = true 152 + sending.value = true
153 - const { code } = await smsAPI({ mobile: phone.value }) 153 + const { code } = await smsAPI({ mobile: phone.value })
154 - if (code) { 154 + if (code) {
155 - showToast('验证码已发送') 155 + showToast('验证码已发送')
156 - start_countdown() 156 + start_countdown()
157 - }
158 - } catch (e) {
159 - showFailToast('网络异常,请稍后重试')
160 - } finally {
161 - sending.value = false
162 } 157 }
158 + } catch (e) {
159 + showFailToast('网络异常,请稍后重试')
160 + } finally {
161 + sending.value = false
162 + }
163 } 163 }
164 164
165 /** 165 /**
...@@ -167,153 +167,166 @@ const on_send_sms = async function () { ...@@ -167,153 +167,166 @@ const on_send_sms = async function () {
167 * @returns {Promise<void>} 167 * @returns {Promise<void>}
168 */ 168 */
169 const on_login = async function () { 169 const on_login = async function () {
170 - if (logging.value) return 170 + if (logging.value) return
171 - if (!is_valid_phone(phone.value)) { 171 + if (!is_valid_phone(phone.value)) {
172 - showFailToast('请输入有效的手机号') 172 + showFailToast('请输入有效的手机号')
173 - return 173 + return
174 - } 174 + }
175 - if (!/^\d{4,6}$/.test(String(code.value || '').trim())) { 175 + if (!/^\d{4}$/.test(String(code.value || '').trim())) {
176 - showFailToast('请输入4~6位数字验证码') 176 + showFailToast('请输入4位数字验证码')
177 - return 177 + return
178 - } 178 + }
179 - try { 179 + try {
180 - logging.value = true 180 + logging.value = true
181 - const { code, data } = await loginAPI({ mobile: phone.value, code: code.value }) 181 + const { code, data } = await loginAPI({ mobile: phone.value, code: code.value })
182 - if (code) { 182 + if (code) {
183 - // 登录成功后,将token存储到cookie中 183 + // 登录成功后,将token存储到cookie中
184 - Cookies.set('token-stdj', data.token, { expires: 7 }) 184 + Cookies.set('token-stdj', data.token, { expires: 7 })
185 - showSuccessToast('登录成功') 185 + showSuccessToast('登录成功')
186 - // 跳转戒子详情页 186 + // 跳转戒子详情页
187 - router.replace({ path: route.query.redirect }) 187 + router.replace({ path: route.query.redirect })
188 - }
189 - } catch (e) {
190 - showFailToast('网络异常,请稍后重试')
191 - } finally {
192 - logging.value = false
193 } 188 }
189 + } catch (e) {
190 + showFailToast('网络异常,请稍后重试')
191 + } finally {
192 + logging.value = false
193 + }
194 } 194 }
195 195
196 +onMounted(() => {
197 +})
198 +
196 // 组件卸载时清理计时器 199 // 组件卸载时清理计时器
197 onUnmounted(function () { 200 onUnmounted(function () {
198 - if (timer_id.value) { 201 + if (timer_id.value) {
199 - clearInterval(timer_id.value) 202 + clearInterval(timer_id.value)
200 - timer_id.value = null 203 + timer_id.value = null
201 - } 204 + }
202 }) 205 })
203 </script> 206 </script>
204 207
205 <style lang="less" scoped> 208 <style lang="less" scoped>
206 // 页面背景与布局 209 // 页面背景与布局
207 .login-page { 210 .login-page {
208 - min-height: 100vh; 211 + min-height: 100vh;
209 - background-color: #FCF8F1; // 整个页面背景色 212 + background-color: #FCF8F1; // 整个页面背景色
210 - display: flex; 213 + display: flex;
211 - flex-direction: column; 214 + flex-direction: column;
212 - align-items: center; 215 + align-items: center;
213 - padding: 6rem 1rem; 216 + padding: 6rem 1rem;
214 - background-image: url('https://cdn.ipadbiz.cn/stdj/images/bg002@2x.png'); 217 + background-image: url('https://cdn.ipadbiz.cn/stdj/images/bg002@2x.png');
215 - background-size: cover; 218 + background-size: cover;
216 - background-position: center; 219 + background-position: center;
217 } 220 }
218 221
219 // 顶部Logo标题 222 // 顶部Logo标题
220 .logo-title { 223 .logo-title {
221 - margin-bottom: 2rem; 224 + margin-bottom: 2rem;
222 } 225 }
226 +
223 .logo-img { 227 .logo-img {
224 - height: 5.5rem; 228 + height: 5.5rem;
225 - object-fit: contain; 229 + object-fit: contain;
226 - margin-top: 1rem; 230 + margin-top: 1rem;
227 } 231 }
228 232
229 // 验证容器 233 // 验证容器
230 .auth-card { 234 .auth-card {
231 - max-width: 26rem; 235 + max-width: 26rem;
232 - background-color: rgba(174, 155, 99, 0.14); // 容器背景色 236 + background-color: rgba(174, 155, 99, 0.14); // 容器背景色
233 - border-radius: 0.75rem; 237 + border-radius: 0.75rem;
234 - padding: 1rem; 238 + padding: 1rem;
235 - box-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.06); 239 + box-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.06);
236 } 240 }
241 +
237 .card-title { 242 .card-title {
238 - text-align: center; 243 + text-align: center;
239 - color: #432C0E; 244 + color: #432C0E;
240 - font-size: 1.125rem; 245 + font-size: 1.125rem;
241 - font-weight: 700; 246 + font-weight: 700;
242 - margin-top: 0.75rem; 247 + margin-top: 0.75rem;
243 - margin-bottom: 1.25rem; 248 + margin-bottom: 1.25rem;
244 } 249 }
245 250
246 // 表单项布局 251 // 表单项布局
247 .form-item { 252 .form-item {
248 - margin-top: 1.25rem; 253 + margin-top: 1.25rem;
249 - margin-bottom: 0.75rem; 254 + margin-bottom: 0.75rem;
250 } 255 }
256 +
251 .code-row { 257 .code-row {
252 - display: flex; 258 + display: flex;
253 - gap: 0.5rem; 259 + gap: 0.5rem;
254 - align-items: center; 260 + align-items: center;
255 } 261 }
256 262
257 // 输入框:带左侧图标 263 // 输入框:带左侧图标
258 .input-with-icon { 264 .input-with-icon {
259 - position: relative; 265 + position: relative;
260 } 266 }
267 +
261 .input-with-icon::before { 268 .input-with-icon::before {
262 - content: ''; 269 + content: '';
263 - position: absolute; 270 + position: absolute;
264 - left: 0.75rem; 271 + left: 0.75rem;
265 - top: 50%; 272 + top: 50%;
266 - width: 1rem; 273 + width: 1rem;
267 - height: 1rem; 274 + height: 1rem;
268 - background-size: contain; 275 + background-size: contain;
269 - background-position: center; 276 + background-position: center;
270 - background-repeat: no-repeat; 277 + background-repeat: no-repeat;
271 - transform: translateY(-50%); 278 + transform: translateY(-50%);
272 } 279 }
280 +
273 .input-with-icon.phone::before { 281 .input-with-icon.phone::before {
274 - background-image: url('https://cdn.ipadbiz.cn/stdj/images/%E6%89%8B%E6%9C%BA@2x.png'); 282 + background-image: url('https://cdn.ipadbiz.cn/stdj/images/%E6%89%8B%E6%9C%BA@2x.png');
275 } 283 }
284 +
276 .input-with-icon.code::before { 285 .input-with-icon.code::before {
277 - background-image: url('https://cdn.ipadbiz.cn/stdj/images/%E9%AA%8C%E8%AF%81%E7%A0%81@2x-1.png'); 286 + background-image: url('https://cdn.ipadbiz.cn/stdj/images/%E9%AA%8C%E8%AF%81%E7%A0%81@2x-1.png');
278 } 287 }
279 288
280 .input { 289 .input {
281 - width: 100%; 290 + width: 100%;
282 - height: 2.5rem; 291 + height: 2.5rem;
283 - border-radius: 0.5rem; 292 + border-radius: 0.5rem;
284 - border: none; 293 + border: none;
285 - background-color: #FFFFFF; 294 + background-color: #FFFFFF;
286 - padding: 0 0.75rem 0 2.25rem; // 为左侧图标预留空间 295 + padding: 0 0.75rem 0 2.25rem; // 为左侧图标预留空间
287 - box-shadow: inset 0 0 0 0.0625rem rgba(0, 0, 0, 0.08); 296 + box-shadow: inset 0 0 0 0.0625rem rgba(0, 0, 0, 0.08);
288 } 297 }
298 +
289 .input::placeholder { 299 .input::placeholder {
290 - color: #999999; 300 + color: #999999;
291 } 301 }
292 302
293 // 按钮样式 303 // 按钮样式
294 .btn { 304 .btn {
295 - height: 2.5rem; 305 + height: 2.5rem;
296 - padding: 0 0.75rem; 306 + padding: 0 0.75rem;
297 - border: none; 307 + border: none;
298 - border-radius: 0.5rem; 308 + border-radius: 0.5rem;
299 - background-color: #A67939; // 获取验证码与立即验证背景色 309 + background-color: #A67939; // 获取验证码与立即验证背景色
300 - color: #FFFFFF; 310 + color: #FFFFFF;
301 - font-weight: 600; 311 + font-weight: 600;
302 - text-align: center; 312 + text-align: center;
303 - line-height: 2.5rem; 313 + line-height: 2.5rem;
304 } 314 }
315 +
305 .btn.send { 316 .btn.send {
306 - white-space: nowrap; 317 + white-space: nowrap;
307 } 318 }
319 +
308 .btn.primary { 320 .btn.primary {
309 - width: 100%; 321 + width: 100%;
310 } 322 }
323 +
311 .btn.disabled { 324 .btn.disabled {
312 - // 不可操作态:明显的灰显与禁止样式 325 + // 不可操作态:明显的灰显与禁止样式
313 - opacity: 0.5; 326 + opacity: 0.5;
314 - cursor: not-allowed; 327 + cursor: not-allowed;
315 - pointer-events: none; 328 + pointer-events: none;
316 - filter: grayscale(30%); 329 + filter: grayscale(30%);
317 - box-shadow: none; 330 + box-shadow: none;
318 } 331 }
319 </style> 332 </style>
......