🎨 complete SMS login function
This commit is contained in:
@@ -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'
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@@ -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',
|
||||
|
@@ -34,6 +34,10 @@ export default {
|
||||
rotateCaptchaTitle: "请拖动滑块完成拼图",
|
||||
systemError: "系统错误!请稍后再试!",
|
||||
captchaExpired: "验证码已过期,请重新获取!",
|
||||
sendCaptchaSuccess: "验证码已发送,请注意查收!",
|
||||
sendCaptchaError: "验证码发送失败,请稍后再试!",
|
||||
loginSuccess: "登录成功!",
|
||||
loginError: "登录失败!",
|
||||
|
||||
|
||||
},
|
||||
|
@@ -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
7
src/types/user.d.ts
vendored
@@ -1,10 +1,3 @@
|
||||
export interface User {
|
||||
accessToken?: string
|
||||
userId?: string
|
||||
refreshToken?: string
|
||||
expiresAt?: number
|
||||
|
||||
}
|
||||
|
||||
export interface AccountLogin {
|
||||
account?: string
|
||||
|
@@ -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;
|
||||
|
@@ -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({
|
||||
|
@@ -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();
|
||||
|
@@ -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";
|
||||
|
Reference in New Issue
Block a user