🎨 complete SMS login function

This commit is contained in:
landaiqing
2024-08-14 00:08:37 +08:00
parent fa1301689a
commit 48d2f61223
9 changed files with 119 additions and 55 deletions

View File

@@ -1,22 +1,61 @@
import {service} from "@/utils/alova/service.ts";
import {PhoneLogin} from "@/types/user";
/**
* 获取用户信息
*/
export const getUserInfo = () => {
return service.Get('/api/auth/user/List', {
meta: {
ignoreToken: false
},
cacheFor: 1000 * 60
});
};
/**
* 刷新token
* @param refreshToken
*/
export const refreshToken = (refreshToken: string) => {
return service.Get('/api/auth/token/refresh', {
params: {
refresh_token: refreshToken
},
meta: {
authRole: 'refreshToken'
authRole: 'refreshToken',
ignoreToken: false
}
});
};
/**
* 发送短信验证码
* @param phone
*/
export const sendMessage = (phone: string) => {
return service.Get('/api/sms/test/send', {
params: {
phone: phone
},
meta: {
ignoreToken: true
}
});
};
/**
* 手机登录
* @param param
*/
export const phoneLoginApi = (param: PhoneLogin) => {
return service.Post('/api/user/phone_login', {
phone: param.phone,
captcha: param.captcha,
},
{
meta: {
ignoreToken: true,
authRole: 'login'
}
}
);
};

View File

@@ -34,7 +34,10 @@ export default {
rotateCaptchaTitle: "Please drag the slider to complete the puzzle",
systemError: "System error, please try again later",
captchaExpired: "captcha expired, please try again",
sendCaptchaSuccess: "captcha sent successfully, please check your phone!",
sendCaptchaError: "captcha sending failed, please try again later",
loginSuccess: "login success",
loginError: "login failed!",
},
error: {
networkError: 'Network error, please try again later',

View File

@@ -34,6 +34,10 @@ export default {
rotateCaptchaTitle: "请拖动滑块完成拼图",
systemError: "系统错误!请稍后再试!",
captchaExpired: "验证码已过期,请重新获取!",
sendCaptchaSuccess: "验证码已发送,请注意查收!",
sendCaptchaError: "验证码发送失败,请稍后再试!",
loginSuccess: "登录成功!",
loginError: "登录失败!",
},

View File

@@ -1,30 +1,19 @@
import {defineStore} from 'pinia';
import {ref} from 'vue';
import {User} from "@/types/user";
import {reactive} from 'vue';
export const useAuthStore = defineStore(
'user',
() => {
const user = ref<User>();
function setUser(data: User) {
user.value = data;
}
function getUser() {
return user.value;
}
function clearUser() {
user.value = void 0;
}
const user: any = reactive({
accessToken: '',
userId: '',
refreshToken: '',
expiresAt: 0,
});
return {
user,
setUser,
getUser,
clearUser
};
},
{
@@ -32,7 +21,7 @@ export const useAuthStore = defineStore(
persist: {
key: 'user',
storage: localStorage,
paths: ["user"],
paths: ['user'],
}
}
);

7
src/types/user.d.ts vendored
View File

@@ -1,10 +1,3 @@
export interface User {
accessToken?: string
userId?: string
refreshToken?: string
expiresAt?: number
}
export interface AccountLogin {
account?: string

View File

@@ -8,7 +8,7 @@ export const localforageStorageAdapter = {
get(key: string) {
let value: any;
localforage.getItem(key).then((res: any) => {
if (res === null || res === undefined) {
if (res === null || res === undefined || res === "") {
value = "";
} else {
value = res;

View File

@@ -24,15 +24,18 @@ const {onAuthRequired, onResponseRefreshToken} = createServerTokenAuthentication
handler: async () => {
try {
const user = useStore().user;
const res: any = await refreshToken(user.getUser()?.refreshToken || '');
const res: any = await refreshToken(user.user?.refreshToken || '');
if (res.code === 0 && res.data) {
user.setUser({
userId: res.data.userId,
accessToken: res.data.access_token,
refreshToken: res.data.refresh_token,
expiresAt: res.data.expires_at,
});
const {uid, access_token, refresh_token, expires_at} = res.data;
user.user.userId = uid;
user.user.accessToken = access_token;
user.user.refreshToken = refresh_token;
user.user.expiresAt = expires_at;
}
// else {
// message.error(res.message);
// await router.push('/login');
// }
} catch (error) {
// token刷新失败跳转回登录页
message.error(i18n.global.t('error.authTokenError')).then();
@@ -55,10 +58,10 @@ export const service = createAlova({
beforeRequest: onAuthRequired(async (method: any) => {
if (!method.meta?.ignoreToken) {
const user = useStore().user;
method.config.headers.Authorization = `${import.meta.env.VITE_APP_TOKEN_KEY} ${user.getUser()?.accessToken}`;
method.config.headers.Authorization = `${import.meta.env.VITE_APP_TOKEN_KEY} ${user.user.accessToken}`;
}
const lang = useStore().lang;
method.config.headers['Accept-Language'] = lang.lang|| 'zh';
method.config.headers['Accept-Language'] = lang.lang || 'zh';
}),
// 响应拦截器
responded: onResponseRefreshToken({

View File

@@ -142,7 +142,7 @@ const state = reactive({
* 验证码发送倒计时
*/
const countDown = () => {
const startTime = localStorage.getItem('startTimeSendCaptcha');
const startTime = localStorage.getItem('start_time_send_captcha');
const nowTime = new Date().getTime();
let surplus: number = 60;
let timer: any;
@@ -150,7 +150,7 @@ const countDown = () => {
surplus = 60 - Math.floor((nowTime - Number(startTime)) / 1000);
surplus = surplus <= 0 ? 0 : surplus;
} else {
localStorage.setItem('startTimeSendCaptcha', String(nowTime));
localStorage.setItem('start_time_send_captcha', String(nowTime));
}
state.countDownTime = surplus;
@@ -160,7 +160,7 @@ const countDown = () => {
}
timer = setInterval(() => {
if (state.countDownTime <= 0) {
localStorage.removeItem('startTimeSendCaptcha');
localStorage.removeItem('start_time_send_captcha');
clearInterval(timer);
state.countDownTime = 60;
state.showCountDown = false;
@@ -171,7 +171,7 @@ const countDown = () => {
}, 1000);
};
onMounted(() => {
const sendEndTime = localStorage.getItem('startTimeSendCaptcha');
const sendEndTime = localStorage.getItem('start_time_send_captcha');
if (sendEndTime) {
state.showCountDown = true;
countDown();

View File

@@ -21,6 +21,7 @@
<span class="login-card-span">{{ t("login.phone") }}</span>
<AInput v-model:value="phoneLoginForm.phone" class="login-form-input" size="large"
:placeholder=phoneValidate allow-clear
autocomplete="off"
>
<template #prefix>
<TabletOutlined/>
@@ -72,13 +73,13 @@
{{ t("login.accountLogin") }}
</span>
</template>
<AForm ref="accountLoginFormRef" :rules="rules" :model="accountLoginForm">
<AForm ref="accountLoginFormRef" :rules="rules" :model="accountLoginForm" autocomplete="off">
<AFormItem
class="login-form-item"
name="account">
<span class="login-card-span">{{ t("login.account") }}</span>
<AInput v-model:value="accountLoginForm.account" class="login-form-input" size="large"
:placeholder=accountValidate allow-clear>
:placeholder=accountValidate allow-clear autocomplete="off">
<template #prefix>
<user-outlined/>
</template>
@@ -90,7 +91,7 @@
<AFlex :vertical="true">
<span class="login-card-span">{{ t("login.password") }}</span>
<AInputPassword v-model:value="accountLoginForm.password" class="login-form-input" size="large"
:placeholder=passwordValidate allow-clear>
:placeholder=passwordValidate allow-clear autocomplete="off">
<template #prefix>
<SafetyOutlined/>
</template>
@@ -151,6 +152,8 @@ import LoginFooter from "@/views/Login/LoginFooter.vue";
import {useRouter} from "vue-router";
import {checkRotatedCaptcha, getRotatedCaptchaData} from "@/api/captcha";
import {message} from "ant-design-vue";
import {phoneLoginApi, sendMessage} from "@/api/user";
import useStore from "@/store";
const router = useRouter();
const {t} = useI18n();
@@ -289,11 +292,22 @@ async function accountLoginSubmit() {
async function phoneLoginSubmit() {
phoneLoginFormRef.value
.validate()
.then(() => {
console.log('values', phoneLoginForm);
.then(async () => {
const res: any = await phoneLoginApi(phoneLoginForm);
if (res.code === 0 && res.success) {
const userStore = useStore().user;
const {uid, access_token, refresh_token, expires_at} = res.data;
userStore.user.userId = uid;
userStore.user.accessToken = access_token;
userStore.user.refreshToken = refresh_token;
userStore.user.expiresAt = expires_at;
message.success(t('login.loginSuccess'));
} else {
message.error(res.message);
}
})
.catch((error: any) => {
console.log('error', error);
console.error(error);
});
}
@@ -303,9 +317,11 @@ async function phoneLoginSubmit() {
async function getRotateCaptcha() {
const data: any = await getRotatedCaptchaData();
if (data.code === 0 && data.data) {
captchaData.image = data.data.image;
captchaData.thumb = data.data.thumb;
captchaData.key = data.data.key;
const {angle, image, thumb, key} = data.data;
captchaData.angle = angle;
captchaData.image = image;
captchaData.thumb = thumb;
captchaData.key = key;
} else {
message.error(t('login.systemError'));
}
@@ -324,9 +340,11 @@ async function checkCaptcha(angle: number) {
} else {
const result: any = await checkRotatedCaptcha(angle, captchaData.key);
if (result.code === 0 && result.success) {
message.success(t('login.captchaSuccess'));
showRotateCaptcha.value = false;
countDown();
const result: boolean = await sendMessageByPhone();
if (result) {
countDown();
}
} else if (result.code === 1011) {
message.error(t('login.captchaExpired'));
getRotateCaptcha().then(() => {
@@ -346,6 +364,21 @@ async function checkCaptcha(angle: number) {
async function closeRotateCaptcha() {
showRotateCaptcha.value = false;
}
/**
* 发送手机验证码
*/
async function sendMessageByPhone(): Promise<boolean> {
const phone: string = phoneLoginForm.phone as string;
const res: any = await sendMessage(phone);
if (res.code === 0 && res.success) {
message.success(t('login.sendCaptchaSuccess'));
return true;
} else {
message.error(res.data);
return false;
}
}
</script>
<style src="./index.scss" scoped>
@import "@/assets/styles/global.scss";