🎨 complete SMS login function
This commit is contained in:
@@ -1,22 +1,61 @@
|
|||||||
import {service} from "@/utils/alova/service.ts";
|
import {service} from "@/utils/alova/service.ts";
|
||||||
|
import {PhoneLogin} from "@/types/user";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户信息
|
||||||
|
*/
|
||||||
export const getUserInfo = () => {
|
export const getUserInfo = () => {
|
||||||
return service.Get('/api/auth/user/List', {
|
return service.Get('/api/auth/user/List', {
|
||||||
meta: {
|
meta: {
|
||||||
ignoreToken: false
|
ignoreToken: false
|
||||||
},
|
},
|
||||||
|
cacheFor: 1000 * 60
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* 刷新token
|
||||||
|
* @param refreshToken
|
||||||
|
*/
|
||||||
export const refreshToken = (refreshToken: string) => {
|
export const refreshToken = (refreshToken: string) => {
|
||||||
return service.Get('/api/auth/token/refresh', {
|
return service.Get('/api/auth/token/refresh', {
|
||||||
params: {
|
params: {
|
||||||
refresh_token: refreshToken
|
refresh_token: refreshToken
|
||||||
},
|
},
|
||||||
meta: {
|
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",
|
rotateCaptchaTitle: "Please drag the slider to complete the puzzle",
|
||||||
systemError: "System error, please try again later",
|
systemError: "System error, please try again later",
|
||||||
captchaExpired: "captcha expired, please try again",
|
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: {
|
error: {
|
||||||
networkError: 'Network error, please try again later',
|
networkError: 'Network error, please try again later',
|
||||||
|
@@ -34,6 +34,10 @@ export default {
|
|||||||
rotateCaptchaTitle: "请拖动滑块完成拼图",
|
rotateCaptchaTitle: "请拖动滑块完成拼图",
|
||||||
systemError: "系统错误!请稍后再试!",
|
systemError: "系统错误!请稍后再试!",
|
||||||
captchaExpired: "验证码已过期,请重新获取!",
|
captchaExpired: "验证码已过期,请重新获取!",
|
||||||
|
sendCaptchaSuccess: "验证码已发送,请注意查收!",
|
||||||
|
sendCaptchaError: "验证码发送失败,请稍后再试!",
|
||||||
|
loginSuccess: "登录成功!",
|
||||||
|
loginError: "登录失败!",
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
@@ -1,30 +1,19 @@
|
|||||||
import {defineStore} from 'pinia';
|
import {defineStore} from 'pinia';
|
||||||
import {ref} from 'vue';
|
import {reactive} from 'vue';
|
||||||
import {User} from "@/types/user";
|
|
||||||
|
|
||||||
|
|
||||||
export const useAuthStore = defineStore(
|
export const useAuthStore = defineStore(
|
||||||
'user',
|
'user',
|
||||||
() => {
|
() => {
|
||||||
const user = ref<User>();
|
const user: any = reactive({
|
||||||
|
accessToken: '',
|
||||||
function setUser(data: User) {
|
userId: '',
|
||||||
user.value = data;
|
refreshToken: '',
|
||||||
}
|
expiresAt: 0,
|
||||||
|
});
|
||||||
function getUser() {
|
|
||||||
return user.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearUser() {
|
|
||||||
user.value = void 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
setUser,
|
|
||||||
getUser,
|
|
||||||
clearUser
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -32,7 +21,7 @@ export const useAuthStore = defineStore(
|
|||||||
persist: {
|
persist: {
|
||||||
key: 'user',
|
key: 'user',
|
||||||
storage: localStorage,
|
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 {
|
export interface AccountLogin {
|
||||||
account?: string
|
account?: string
|
||||||
|
@@ -8,7 +8,7 @@ export const localforageStorageAdapter = {
|
|||||||
get(key: string) {
|
get(key: string) {
|
||||||
let value: any;
|
let value: any;
|
||||||
localforage.getItem(key).then((res: any) => {
|
localforage.getItem(key).then((res: any) => {
|
||||||
if (res === null || res === undefined) {
|
if (res === null || res === undefined || res === "") {
|
||||||
value = "";
|
value = "";
|
||||||
} else {
|
} else {
|
||||||
value = res;
|
value = res;
|
||||||
|
@@ -24,15 +24,18 @@ const {onAuthRequired, onResponseRefreshToken} = createServerTokenAuthentication
|
|||||||
handler: async () => {
|
handler: async () => {
|
||||||
try {
|
try {
|
||||||
const user = useStore().user;
|
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) {
|
if (res.code === 0 && res.data) {
|
||||||
user.setUser({
|
const {uid, access_token, refresh_token, expires_at} = res.data;
|
||||||
userId: res.data.userId,
|
user.user.userId = uid;
|
||||||
accessToken: res.data.access_token,
|
user.user.accessToken = access_token;
|
||||||
refreshToken: res.data.refresh_token,
|
user.user.refreshToken = refresh_token;
|
||||||
expiresAt: res.data.expires_at,
|
user.user.expiresAt = expires_at;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
// else {
|
||||||
|
// message.error(res.message);
|
||||||
|
// await router.push('/login');
|
||||||
|
// }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// token刷新失败,跳转回登录页
|
// token刷新失败,跳转回登录页
|
||||||
message.error(i18n.global.t('error.authTokenError')).then();
|
message.error(i18n.global.t('error.authTokenError')).then();
|
||||||
@@ -55,10 +58,10 @@ export const service = createAlova({
|
|||||||
beforeRequest: onAuthRequired(async (method: any) => {
|
beforeRequest: onAuthRequired(async (method: any) => {
|
||||||
if (!method.meta?.ignoreToken) {
|
if (!method.meta?.ignoreToken) {
|
||||||
const user = useStore().user;
|
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;
|
const lang = useStore().lang;
|
||||||
method.config.headers['Accept-Language'] = lang.lang|| 'zh';
|
method.config.headers['Accept-Language'] = lang.lang || 'zh';
|
||||||
}),
|
}),
|
||||||
// 响应拦截器
|
// 响应拦截器
|
||||||
responded: onResponseRefreshToken({
|
responded: onResponseRefreshToken({
|
||||||
|
@@ -142,7 +142,7 @@ const state = reactive({
|
|||||||
* 验证码发送倒计时
|
* 验证码发送倒计时
|
||||||
*/
|
*/
|
||||||
const countDown = () => {
|
const countDown = () => {
|
||||||
const startTime = localStorage.getItem('startTimeSendCaptcha');
|
const startTime = localStorage.getItem('start_time_send_captcha');
|
||||||
const nowTime = new Date().getTime();
|
const nowTime = new Date().getTime();
|
||||||
let surplus: number = 60;
|
let surplus: number = 60;
|
||||||
let timer: any;
|
let timer: any;
|
||||||
@@ -150,7 +150,7 @@ const countDown = () => {
|
|||||||
surplus = 60 - Math.floor((nowTime - Number(startTime)) / 1000);
|
surplus = 60 - Math.floor((nowTime - Number(startTime)) / 1000);
|
||||||
surplus = surplus <= 0 ? 0 : surplus;
|
surplus = surplus <= 0 ? 0 : surplus;
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem('startTimeSendCaptcha', String(nowTime));
|
localStorage.setItem('start_time_send_captcha', String(nowTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.countDownTime = surplus;
|
state.countDownTime = surplus;
|
||||||
@@ -160,7 +160,7 @@ const countDown = () => {
|
|||||||
}
|
}
|
||||||
timer = setInterval(() => {
|
timer = setInterval(() => {
|
||||||
if (state.countDownTime <= 0) {
|
if (state.countDownTime <= 0) {
|
||||||
localStorage.removeItem('startTimeSendCaptcha');
|
localStorage.removeItem('start_time_send_captcha');
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
state.countDownTime = 60;
|
state.countDownTime = 60;
|
||||||
state.showCountDown = false;
|
state.showCountDown = false;
|
||||||
@@ -171,7 +171,7 @@ const countDown = () => {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const sendEndTime = localStorage.getItem('startTimeSendCaptcha');
|
const sendEndTime = localStorage.getItem('start_time_send_captcha');
|
||||||
if (sendEndTime) {
|
if (sendEndTime) {
|
||||||
state.showCountDown = true;
|
state.showCountDown = true;
|
||||||
countDown();
|
countDown();
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
<span class="login-card-span">{{ t("login.phone") }}</span>
|
<span class="login-card-span">{{ t("login.phone") }}</span>
|
||||||
<AInput v-model:value="phoneLoginForm.phone" class="login-form-input" size="large"
|
<AInput v-model:value="phoneLoginForm.phone" class="login-form-input" size="large"
|
||||||
:placeholder=phoneValidate allow-clear
|
:placeholder=phoneValidate allow-clear
|
||||||
|
autocomplete="off"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<TabletOutlined/>
|
<TabletOutlined/>
|
||||||
@@ -72,13 +73,13 @@
|
|||||||
{{ t("login.accountLogin") }}
|
{{ t("login.accountLogin") }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<AForm ref="accountLoginFormRef" :rules="rules" :model="accountLoginForm">
|
<AForm ref="accountLoginFormRef" :rules="rules" :model="accountLoginForm" autocomplete="off">
|
||||||
<AFormItem
|
<AFormItem
|
||||||
class="login-form-item"
|
class="login-form-item"
|
||||||
name="account">
|
name="account">
|
||||||
<span class="login-card-span">{{ t("login.account") }}</span>
|
<span class="login-card-span">{{ t("login.account") }}</span>
|
||||||
<AInput v-model:value="accountLoginForm.account" class="login-form-input" size="large"
|
<AInput v-model:value="accountLoginForm.account" class="login-form-input" size="large"
|
||||||
:placeholder=accountValidate allow-clear>
|
:placeholder=accountValidate allow-clear autocomplete="off">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<user-outlined/>
|
<user-outlined/>
|
||||||
</template>
|
</template>
|
||||||
@@ -90,7 +91,7 @@
|
|||||||
<AFlex :vertical="true">
|
<AFlex :vertical="true">
|
||||||
<span class="login-card-span">{{ t("login.password") }}</span>
|
<span class="login-card-span">{{ t("login.password") }}</span>
|
||||||
<AInputPassword v-model:value="accountLoginForm.password" class="login-form-input" size="large"
|
<AInputPassword v-model:value="accountLoginForm.password" class="login-form-input" size="large"
|
||||||
:placeholder=passwordValidate allow-clear>
|
:placeholder=passwordValidate allow-clear autocomplete="off">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<SafetyOutlined/>
|
<SafetyOutlined/>
|
||||||
</template>
|
</template>
|
||||||
@@ -151,6 +152,8 @@ import LoginFooter from "@/views/Login/LoginFooter.vue";
|
|||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {checkRotatedCaptcha, getRotatedCaptchaData} from "@/api/captcha";
|
import {checkRotatedCaptcha, getRotatedCaptchaData} from "@/api/captcha";
|
||||||
import {message} from "ant-design-vue";
|
import {message} from "ant-design-vue";
|
||||||
|
import {phoneLoginApi, sendMessage} from "@/api/user";
|
||||||
|
import useStore from "@/store";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const {t} = useI18n();
|
const {t} = useI18n();
|
||||||
@@ -289,11 +292,22 @@ async function accountLoginSubmit() {
|
|||||||
async function phoneLoginSubmit() {
|
async function phoneLoginSubmit() {
|
||||||
phoneLoginFormRef.value
|
phoneLoginFormRef.value
|
||||||
.validate()
|
.validate()
|
||||||
.then(() => {
|
.then(async () => {
|
||||||
console.log('values', phoneLoginForm);
|
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) => {
|
.catch((error: any) => {
|
||||||
console.log('error', error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,9 +317,11 @@ async function phoneLoginSubmit() {
|
|||||||
async function getRotateCaptcha() {
|
async function getRotateCaptcha() {
|
||||||
const data: any = await getRotatedCaptchaData();
|
const data: any = await getRotatedCaptchaData();
|
||||||
if (data.code === 0 && data.data) {
|
if (data.code === 0 && data.data) {
|
||||||
captchaData.image = data.data.image;
|
const {angle, image, thumb, key} = data.data;
|
||||||
captchaData.thumb = data.data.thumb;
|
captchaData.angle = angle;
|
||||||
captchaData.key = data.data.key;
|
captchaData.image = image;
|
||||||
|
captchaData.thumb = thumb;
|
||||||
|
captchaData.key = key;
|
||||||
} else {
|
} else {
|
||||||
message.error(t('login.systemError'));
|
message.error(t('login.systemError'));
|
||||||
}
|
}
|
||||||
@@ -324,9 +340,11 @@ async function checkCaptcha(angle: number) {
|
|||||||
} else {
|
} else {
|
||||||
const result: any = await checkRotatedCaptcha(angle, captchaData.key);
|
const result: any = await checkRotatedCaptcha(angle, captchaData.key);
|
||||||
if (result.code === 0 && result.success) {
|
if (result.code === 0 && result.success) {
|
||||||
message.success(t('login.captchaSuccess'));
|
|
||||||
showRotateCaptcha.value = false;
|
showRotateCaptcha.value = false;
|
||||||
countDown();
|
const result: boolean = await sendMessageByPhone();
|
||||||
|
if (result) {
|
||||||
|
countDown();
|
||||||
|
}
|
||||||
} else if (result.code === 1011) {
|
} else if (result.code === 1011) {
|
||||||
message.error(t('login.captchaExpired'));
|
message.error(t('login.captchaExpired'));
|
||||||
getRotateCaptcha().then(() => {
|
getRotateCaptcha().then(() => {
|
||||||
@@ -346,6 +364,21 @@ async function checkCaptcha(angle: number) {
|
|||||||
async function closeRotateCaptcha() {
|
async function closeRotateCaptcha() {
|
||||||
showRotateCaptcha.value = false;
|
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>
|
</script>
|
||||||
<style src="./index.scss" scoped>
|
<style src="./index.scss" scoped>
|
||||||
@import "@/assets/styles/global.scss";
|
@import "@/assets/styles/global.scss";
|
||||||
|
Reference in New Issue
Block a user