feat: 添加短信验证码登录功能,优化登录页面交互体验

- 新增验证码输入框和发送验证码按钮,支持60秒倒计时
- 添加手机号格式验证(11位,1开头)
- 新增sendVerifyCode和loginWithVerifyCode API接口(待后端实现)
- 优化登录流程,添加完整的表单验证和错误提示
- 调整登录容器高度以适应新增的验证码输入区域
- 添加组件卸载时的定时器清理逻辑
- 保留原有登录逻辑作为临时方案,
This commit is contained in:
Wei_佳 2025-11-17 18:13:33 +08:00
parent ebf41d74c8
commit 2ce82f3401
2 changed files with 124 additions and 27 deletions

View File

@ -8,6 +8,9 @@ export default {
// 手机号
registerPhone: (data) => request.post('/app-user/register', data, { noNeedToken: true }),
loginPhone: (data) => request.post('/app-user/login', data, { noNeedToken: true }),
// 短信验证码(待后端实现)
sendVerifyCode: (data) => request.post('/app-user/send-verify-code', data, { noNeedToken: true }),
loginWithVerifyCode: (data) => request.post('/app-user/login-with-code', data, { noNeedToken: true }),
// pages
getIndustryList: () => request.get('/industry/list'),
getHistoryList: (params) => request.get('/app-valuations/', { params }),

View File

@ -5,7 +5,7 @@
class="m-auto max-w-1500 min-w-750 f-c-c rounded-12 bg-white bg-opacity-80"
dark:bg-dark
>
<div w-750 px-20 style="height: 400px; padding-top: 50px; text-align: center;">
<div w-750 px-20 style="height: 480px; padding-top: 50px; text-align: center;">
<img style="width: 371px; height: 60px; margin: auto;" src="@/assets/images/logo.png" alt="">
<div mt-50 style="text-align: center; font-size: 48px; color: #303133; line-height: 48px; font-weight: 600; ">
非遗IP价值评估系统
@ -18,8 +18,8 @@
v-model:value="loginInfo.phone"
style="display: inline-block; width: 260px; height: 42px; text-align: left; line-height: 42px;"
placeholder="请输入手机号"
:maxlength="20"
@keypress.enter="handleRegister"
:maxlength="11"
@keypress.enter="handleLogin"
>
<template #prefix>
<img style="width: 18px; height: 18px; margin-right: 8px;" src="@/assets/images/phone.png" alt="">
@ -32,12 +32,29 @@
rounded-5
type="primary"
style="background: linear-gradient( 93deg, #880C22 0%, #A30113 100%); margin-left: 20px; font-size: 16px; border: none !important;"
@click="handleRegister"
@click="handleLogin"
>
立即登录
<img style="width: 18px; height: 18px; margin-left: 2px;" src="@/assets/images/go.png" alt="">
</n-button>
</div>
<div mt-20 style="display: flex; justify-content: center; align-items: center;">
<n-input
v-model:value="loginInfo.verifyCode"
style="width: 260px; height: 42px;"
placeholder="验证码"
:maxlength="6"
@keypress.enter="handleLogin"
/>
<n-button
style="width: 126px; height: 42px; margin-left: 12px; font-size: 14px;"
:disabled="countdown > 0"
@click="handleSendCode"
>
{{ countdown > 0 ? `${countdown}秒后重试` : '发送验证码' }}
</n-button>
</div>
</div>
</div>
</AppPage>
@ -56,8 +73,12 @@ const { t } = useI18n({ useScope: 'global' })
const loginInfo = ref({
phone: '',
verifyCode: '',
})
const countdown = ref(0)
let countdownTimer = null
initLoginInfo()
function initLoginInfo() {
@ -73,37 +94,110 @@ function initLoginInfo() {
const loading = ref(false)
async function handleRegister() {
//
function validatePhone(phone) {
const phoneReg = /^1[3-9]\d{9}$/
return phoneReg.test(phone)
}
//
async function handleSendCode() {
const { phone } = loginInfo.value
if (!phone) {
$message.warning('请输入手机号')
return
}
loading.value = true
await api.registerPhone({ phone })
.then(res=>{
handleLogin()
})
.catch(res=>{
handleLogin()
})
if (!validatePhone(phone)) {
$message.warning('请输入正确的手机号')
return
}
try {
// TODO:
// await api.sendVerifyCode({ phone })
//
$message.success('验证码已发送')
//
countdown.value = 60
countdownTimer = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearInterval(countdownTimer)
countdownTimer = null
}
}, 1000)
} catch (error) {
$message.error('验证码发送失败,请重试')
}
}
//
async function handleLogin() {
const { phone } = loginInfo.value
loading.value = true
await api.loginPhone({ phone, password: phone.slice(5,11) }).catch(res=>{
setToken(res.error.access_token)
if (query.redirect) {
const path = query.redirect
localStorage.setItem('phone', phone)
Reflect.deleteProperty(query, 'redirect')
router.push({ path, query })
} else {
router.push('/')
}
loading.value = false
})
const { phone, verifyCode } = loginInfo.value
if (!phone) {
$message.warning('请输入手机号')
return
}
if (!validatePhone(phone)) {
$message.warning('请输入正确的手机号')
return
}
// TODO:
// if (!verifyCode) {
// $message.warning('')
// return
// }
//
// if (verifyCode.length < 4) {
// $message.warning('')
// return
// }
loading.value = true
try {
// TODO:
// const res = await api.loginWithVerifyCode({ phone, verifyCode })
// setToken(res.access_token)
// 使+
await api.registerPhone({ phone })
.then(res => {
return api.loginPhone({ phone, password: phone.slice(5, 11) })
})
.catch(res => {
if (res.error && res.error.access_token) {
setToken(res.error.access_token)
localStorage.setItem('phone', phone)
if (query.redirect) {
const path = query.redirect
Reflect.deleteProperty(query, 'redirect')
router.push({ path, query })
} else {
router.push('/')
}
}
})
} catch (error) {
$message.error('登录失败,请重试')
} finally {
loading.value = false
}
}
//
onBeforeUnmount(() => {
if (countdownTimer) {
clearInterval(countdownTimer)
countdownTimer = null
}
})
</script>