实现一个登录
以pc端为例,实现一个验证码登录
实现一个模板
pc/views/sms
<template>
<div class="view-account">
<div class="view-account-header"></div>
<div class="view-account-container">
<div class="view-account-top">
<h2>请先验证手机:</h2>
</div>
<div class="view-account-form">
<el-form
ref="formRef"
label-placement="left"
size="large"
:model="formInline"
:rules="rules"
:disabled="loading.confirm"
>
<el-form-item prop="phoneNum">
<el-input v-model.trim="formInline.phoneNum" placeholder="手机号">
<template #prefix>+86</template>
</el-input>
</el-form-item>
<el-form-item prop="sms">
<el-input v-model.trim="formInline.sms" placeholder="验证码" clearable>
<template #append>
<el-button
@click="validateForm('phoneNum', getIdCode)"
:disabled="lock || !formInline.phoneNum || loading.confirm"
:loading="loading.id"
>{{ smsMsg }}
</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item>
<div
style="display: flex; justify-content: center; width: 100%"
@click="submitForm(formRef)"
>
<el-button
type="primary"
size="large"
style="flex: 1"
:loading="loading.confirm"
block
:disabled="!formInline.phoneNum || !formInline.sms"
>
确 定
</el-button>
</div>
</el-form-item>
</el-form>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import useSMS from '@/hooks/useSMS';
import { onBeforeUnmount } from 'vue';
const {
formRef,
loading,
formInline,
rules,
lock,
smsMsg,
clearCounting,
getIdCode,
submitForm,
} = useSMS();
const validateForm = async (name: string, callback?) => {
if (!formRef.value) return;
formRef.value.validateField([name], async (valid: boolean) => {
if (valid) return;
if (callback) callback();
});
};
onBeforeUnmount(() => {
clearCounting();
});
</script>
<style lang="less" scoped>
.view-account {
display: flex;
flex-direction: column;
height: 100vh;
overflow: auto;
&-container {
flex: 1;
padding: 64px 0;
width: 384px;
margin: auto;
}
&-top {
padding: 16px 0;
text-align: left;
&-desc {
font-size: 14px;
color: #808695;
}
}
&-form {
:deep(.el-input--prefix .el-input__inner) {
padding-left: 48px;
}
}
&-other {
width: 100%;
}
}
@media (min-width: 768px) {
.view-account {
background-image: url('../../../assets/images/login.svg');
background-repeat: no-repeat;
background-position: 50%;
background-size: 100%;
}
.page-account-container {
padding: 32px 0 24px 0;
}
}
</style>
效果:
逻辑代码
注意:由于项目分为pc端和移动端,大部分数据定义及逻辑代码都写在了useSMS这个hook中,方便复用,我们看下@/hooks/useSMS
import { useRoute, useRouter } from 'vue-router';
import { sendSMSCode, verifySMSCode } from '@/api/sms';
import { useUserStore } from '@/store/modules/user';
interface FormState {
phoneNum: string;
sms: string;
}
export default function () {
const router = useRouter();
const route = useRoute();
const userStore = useUserStore();
const lockTime = ref(60);
const lock = ref(false);
const formRef = ref();
const loading = reactive({
id: false,
confirm: false,
});
const formInline = reactive<FormState>({
phoneNum: '',
sms: '',
});
const rules = {
phoneNum: [
{ required: true, message: '请输入手机号' },
{ required: true, pattern: /^(?:(?:\+|00)86)?1[3-9]\d{9}$/, message: '请输入正确手机号' },
],
sms: [{ required: true, message: '请输入验证码' }],
};
const smsMsg = computed(() => {
return lock.value ? `${lockTime.value}秒后重新获取` : '获取验证码';
});
const setLockTime = (initTime = 60) => {
lockTime.value = initTime;
};
const setLock = (is = true) => {
lock.value = is;
};
let timer;
const timekeeping = () => {
clearInterval(timer);
setLock();
// 重置时间
setLockTime();
timer = setInterval(() => {
// 倒计时递减
setLockTime(lockTime.value - 1);
if (lockTime.value <= 0) {
setLock(false);
return clearInterval(timer);
}
}, 1000);
};
const clearCounting = () => {
clearInterval(timer);
};
const getIdCode = async () => {
try {
loading.id = true;
const { data } = await sendSMSCode({ tel: formInline.phoneNum });
if (data) {
timekeeping();
}
} catch (error) {
} finally {
loading.id = false;
}
};
const submitForm = (formEl) => {
if (!formEl) return;
formEl.validate((valid: boolean) => {
onConfirm(valid);
});
};
const onConfirm = async (valid: boolean) => {
if (!valid) return;
try {
loading.confirm = true;
const { data } = await verifySMSCode({
tel: formInline.phoneNum,
code: formInline.sms,
foreignUserId: userStore.getForeignUserId,
productCode: userStore.getProductCode,
userName: userStore.getUserFaultUserName,
});
if (data) {
userStore.setTel(formInline.phoneNum);
userStore.setId(data);
const toPath = decodeURIComponent((route.query?.redirect || '/') as string);
window['$message'].success('验证通过');
router.replace({
path: toPath,
query: { ...route.query, redirect: undefined, page: undefined },
});
} else {
window['$message'].warning('验证失败');
}
} finally {
loading.confirm = false;
}
};
return {
formRef,
loading,
formInline,
rules,
lockTime,
lock,
smsMsg,
timekeeping,
clearCounting,
getIdCode,
submitForm,
onConfirm,
};
}
结合模板和逻辑代码
- 输手机号;
- 校验格式;
- 获取验证码;
- 60秒倒计时;
- 请求验证码;
- 输入验证码;
- 验证验证码;
- 路由跳转;
基本的功能这样就实现了。
路由拦截
import { useUserStoreWidthOut } from '@/store/modules/user';
const LOGIN_PATH = '/sms';
router.beforeEach(async (to, from, next) => {
const userStore = useUserStoreWidthOut();
if (!userStore.getUserId || !userStore.getUserTel) {
// redirect login page
const redirectData: { path: string; replace: boolean; query?: Recordable<string> } = {
path: LOGIN_PATH,
replace: true,
};
if (to.path) {
redirectData.query = {
...to.query,
redirect: to.path,
};
}
next(redirectData);
return;
}
}
当缺少用户信息时进行路由拦截,跳转验证码登录,并讲原本的路由保存到query上,以便登录成功后跳回原本的页面。
这样一个验证码登录就做好了。