scan the QR code to log in on the WeChat public account

This commit is contained in:
landaiqing
2024-08-15 23:57:12 +08:00
parent cab3b1ee96
commit 3140eaca99
11 changed files with 432 additions and 45 deletions

37
src/api/oauth/index.ts Normal file
View File

@@ -0,0 +1,37 @@
import {service} from "@/utils/alova/service.ts";
/**
* 生成客户端id
*/
export const generateClientId = () => {
return service.Get('/api/oauth/generate_client_id',
{
meta: {
ignoreToken: true,
}
}
);
};
/**
* 获取临时二维码
* @param clientId
*/
export const generateQrCode = (clientId: string) => {
return service.Get('/api/oauth/get_temp_qrcode',
{
params: {
client_id: clientId
},
meta: {
ignoreToken: true,
},
cacheFor: {
// 设置缓存模式为持久化模式
mode: 'restore',
// 缓存时间
expire: 30 * 24 * 60 * 60 * 1000,
tag: 'v1'
}
}
);
};

View File

@@ -1,5 +1,5 @@
import {service} from "@/utils/alova/service.ts";
import {PhoneLogin} from "@/types/user";
import {AccountLogin, PhoneLogin, ResetPassword} from "@/types/user";
/**
* 获取用户信息
@@ -64,3 +64,38 @@ export const phoneLoginApi = (param: PhoneLogin) => {
}
);
};
/**
* 账号登录
* @param param
*/
export const accountLoginApi = (param: AccountLogin) => {
return service.Post('/api/user/login', {
account: param.account,
password: param.password,
},
{
meta: {
ignoreToken: true,
authRole: 'login'
}
}
);
};
/**
* 重置密码
* @param param
*/
export const resetPasswordApi = (param: ResetPassword) => {
return service.Post('/api/user/reset_password', {
phone: param.phone,
captcha: param.captcha,
password: param.password,
repassword: param.repassword
},
{
meta: {
ignoreToken: true,
}
}
);
};

View File

@@ -0,0 +1,77 @@
<svg t="1710925923326" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8087"
width="200" height="200">
<path
d="M481.866 554.39C307.518 329.956 137.124 467.312 87.72 516.212c-8.342 8.258-4.326 23.352 8.838 33.298 79.538 60.1 340.138 238.074 393.422 34.128 2.33-8.918-0.66-19.652-8.114-29.248z"
fill="#CCC033" p-id="8088"></path>
<path
d="M481.866 554.388C307.518 329.956 137.124 467.312 87.72 516.212c-5.48 5.424-5.598 13.796-1.32 21.726 78.098-41.21 195.85-57.526 315.466 96.452 7.456 9.598 10.446 20.33 8.116 29.25a207.5 207.5 0 0 1-3.572 12.14c38.236-8.552 68.836-35.732 83.572-92.14 2.328-8.92-0.662-19.654-8.116-29.252z"
fill="#99AF17"></path>
<path
d="M608.99 589.628c107.374 263.132 308.492 176.808 369.252 143.04 10.26-5.702 10.462-21.32 0.468-34.448-60.388-79.32-263.344-320.97-369.648-138.946-4.648 7.96-4.662 19.102-0.072 30.354z"
fill="#EACF32" p-id="8090"></path>
<path
d="M820.558 537.238c-74.148-52.542-155.996-73-211.496 22.036-4.648 7.96-4.664 19.102-0.072 30.354 46.838 114.78 111.512 163.062 174.594 178.224-25.672-20.84-50.152-50.102-71.994-90.258 15.608-16.372 25.21-38.522 25.21-62.93a90.812 90.812 0 0 0-17.646-53.916 135.364 135.364 0 0 0-7.852 7.886c32.054-34.944 70.066-41.51 109.256-31.396z m-128.932 98.73z m17.05 36.168z m-16.4-34.654z m2.712 6.244z m2.65 5.892z m2.686 5.77z m2.722 5.648z m2.786 5.59z m1.73-93.752z m-18.5 26.4z m4.286-6.992z m3.516-5.332z m3.498-4.94z m3.564-4.688z"
fill="#CCC033" p-id="8091"></path>
<path
d="M65.508 873.848C533.44 700.74 756.58 451.128 824.804 362c13.046-17.044 7.766-41.7-11.132-51.876l-0.006-0.004c-15.116-8.14-33.912-4.076-44.27 9.616-55.86 73.84-256.384 304.032-719.526 516.368-10.962 5.026-15.234 18.396-9.188 28.83 4.996 8.622 15.482 12.37 24.826 8.914z"
fill="#A56021" p-id="8092"></path>
<path d="M494.452 523.422m-91.24 0a91.24 91.24 0 1 0 182.48 0 91.24 91.24 0 1 0-182.48 0Z" fill="#FF4B34"
p-id="8093"></path>
<path d="M469.542 645.53m-91.24 0a91.24 91.24 0 1 0 182.48 0 91.24 91.24 0 1 0-182.48 0Z" fill="#FF624B"
p-id="8094"></path>
<path
d="M61.034 874.934c455.172-210.888 652.954-437.954 708.36-511.196 10.358-13.692 29.154-17.756 44.27-9.616l0.006 0.004a34.662 34.662 0 0 1 10.486 8.712l0.646-0.838c13.046-17.044 7.766-41.698-11.132-51.876l-0.006-0.004c-15.116-8.14-33.912-4.076-44.27 9.616-55.86 73.84-256.384 304.032-719.526 516.368-10.962 5.026-15.234 18.396-9.188 28.832 4.21 7.262 12.316 11.046 20.354 9.998z"
fill="#8C4C17" p-id="8095"></path>
<path
d="M528.2 528.198c-243.476-146.586-127.232-332.026-84.516-386.866 7.214-9.26 22.676-7.056 34.11 4.84 69.084 71.874 276.624 309.598 80.41 386.626-8.58 3.368-19.592 1.668-30.004-4.6z"
fill="#CCC033" p-id="8096"></path>
<path d="M479.496 727.3m-91.24 0a91.24 91.24 0 1 0 182.48 0 91.24 91.24 0 1 0-182.48 0Z" fill="#FF624B"
p-id="8097"></path>
<path
d="M456.826 708.832c24.952-30.628 65.664-40.844 100.914-28.496a91.066 91.066 0 0 0-20.616-23.774c-39.068-31.828-96.538-25.958-128.366 13.11-31.826 39.068-25.958 96.54 13.11 128.366a91.02 91.02 0 0 0 27.452 15.384c-19.22-32.026-17.446-73.962 7.506-104.59z"
fill="#FF4B34" p-id="8098"></path>
<path
d="M490.916 794.086a12.004 12.004 0 0 1-11.27-7.88 11.998 11.998 0 0 1 7.148-15.392c17.892-6.544 29.342-15.336 34.034-26.128 4.95-11.388 0.736-21.612 0.554-22.042-2.728-6.04-0.014-13.084 6.026-15.812 6.04-2.73 13.176 0.02 15.904 6.06 0.896 1.986 8.51 20.002-0.222 40.774-7.29 17.338-23.456 30.692-48.05 39.688a12.046 12.046 0 0 1-4.124 0.732z"
fill="#FF9079" p-id="8099"></path>
<path d="M403.212 559.58m-91.24 0a91.24 91.24 0 1 0 182.48 0 91.24 91.24 0 1 0-182.48 0Z" fill="#FF624B"
p-id="8100"></path>
<path
d="M375.942 570.134c-14.258-36.844-2.978-77.274 25.202-101.786a91.07 91.07 0 0 0-30.864 6.142c-46.994 18.188-70.348 71.026-52.16 118.022 18.188 46.994 71.028 70.348 118.022 52.16a91.034 91.034 0 0 0 26.958-16.234c-37.34 0.842-72.9-21.462-87.158-58.304z"
fill="#FF4B34" p-id="8101"></path>
<path
d="M456.478 588.782a12 12 0 0 1-11.846-14c3.204-18.978 1.164-33.372-6.064-42.784-7.416-9.656-18.42-11.092-18.528-11.102a12 12 0 0 1 2.246-23.894c2.168 0.204 21.592 2.508 35.316 20.378 11.456 14.916 15.054 35.574 10.694 61.398a12.004 12.004 0 0 1-11.818 10.004z"
fill="#FF9079" p-id="8102"></path>
<path
d="M491.586 160.806a1040.528 1040.528 0 0 0-13.792-14.636c-11.434-11.896-26.898-14.1-34.11-4.838-42.716 54.84-158.958 240.28 84.516 386.866 10.412 6.268 21.424 7.968 30.004 4.6 8.192-3.216 15.664-6.718 22.486-10.47-208.196-131.322-136.172-291.618-89.104-361.522z"
fill="#99AF17" p-id="8103"></path>
<path d="M605.562 614.662m-91.24 0a91.24 91.24 0 1 0 182.48 0 91.24 91.24 0 1 0-182.48 0Z" fill="#FF624B"
p-id="8104"></path>
<path
d="M576.32 614.664c0-39.506 25.112-73.14 60.24-85.83a91.062 91.062 0 0 0-31-5.41c-50.39 0-91.24 40.85-91.24 91.24 0 50.39 40.85 91.24 91.24 91.24 10.886 0 21.322-1.914 31-5.412-35.128-12.69-60.24-46.324-60.24-85.828z"
fill="#FF4B34" p-id="8105"></path>
<path
d="M649.002 661.926a12 12 0 0 1-10.302-18.136c9.74-16.376 13.064-30.422 9.884-41.752-3.356-11.954-13.08-17.222-13.492-17.44-5.93-2.96-8.276-10.134-5.316-16.064 2.958-5.93 10.224-8.306 16.158-5.346 1.95 0.974 19.232 10.132 25.582 31.75 5.3 18.046 1.2 38.61-12.188 61.12a11.992 11.992 0 0 1-10.326 5.868z"
fill="#FF9079" p-id="8106"></path>
<path d="M378.3 797.142m-91.24 0a91.24 91.24 0 1 0 182.48 0 91.24 91.24 0 1 0-182.48 0Z" fill="#FF624B"
p-id="8107"></path>
<path
d="M349.704 791.034c8.252-38.634 39.836-66.28 76.842-71.352a91.028 91.028 0 0 0-29.186-11.768c-49.28-10.526-97.762 20.888-108.288 70.168-10.526 49.278 20.888 97.762 70.168 108.288a91.04 91.04 0 0 0 31.446 1.184c-31.702-19.75-49.234-57.886-40.982-96.52z"
fill="#FF4B34" p-id="8108"></path>
<path
d="M413.426 852.702a12 12 0 0 1-8.802-20.152c12.944-13.98 19.13-27.022 18.388-38.766-0.784-12.392-9.194-19.574-9.55-19.874-5.18-4.134-5.976-11.64-1.842-16.822 4.134-5.18 11.736-5.986 16.918-1.854 1.704 1.358 16.69 13.926 18.384 36.396 1.414 18.756-6.892 38.008-24.686 57.224a11.968 11.968 0 0 1-8.81 3.848z"
fill="#FF9079" p-id="8109"></path>
<path d="M277.996 678.872m-91.24 0a91.24 91.24 0 1 0 182.48 0 91.24 91.24 0 1 0-182.48 0Z" fill="#FF624B"
p-id="8110"></path>
<path
d="M248.754 678.872c0-39.506 25.112-73.14 60.24-85.83a91.062 91.062 0 0 0-31-5.41c-50.39 0-91.24 40.85-91.24 91.24 0 50.39 40.85 91.24 91.24 91.24 10.886 0 21.322-1.914 31-5.412-35.128-12.688-60.24-46.322-60.24-85.828z"
fill="#FF4B34" p-id="8111"></path>
<path
d="M321.438 726.136a12 12 0 0 1-10.302-18.136c9.738-16.374 13.064-30.422 9.884-41.752-3.356-11.956-13.08-17.222-13.492-17.44-5.93-2.96-8.276-10.136-5.316-16.064 2.96-5.93 10.226-8.306 16.158-5.346 1.95 0.974 19.232 10.132 25.582 31.752 5.3 18.046 1.2 38.61-12.186 61.12a12.006 12.006 0 0 1-10.328 5.866z"
fill="#FF9079" p-id="8112"></path>
<path d="M557.74 439.404m-30 0a30 30 0 1 0 60 0 30 30 0 1 0-60 0Z" fill="#99AF17" p-id="8113"></path>
<path d="M494.478 368.528m-30 0a30 30 0 1 0 60 0 30 30 0 1 0-60 0Z" fill="#99AF17" p-id="8114"></path>
<path d="M785.856 674.864m-30 0a30 30 0 1 0 60 0 30 30 0 1 0-60 0Z" fill="#CCC033" p-id="8115"></path>
<path
d="M927.274 710.09a11.966 11.966 0 0 1-8.664-3.696l-65.998-68.844a12 12 0 0 1 17.324-16.608l65.998 68.844a12 12 0 0 1-8.66 20.304z"
fill="#FCE575" p-id="8116"></path>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -38,6 +38,9 @@ export default {
sendCaptchaError: "captcha sending failed, please try again later",
loginSuccess: "login success",
loginError: "login failed!",
twoPasswordNotSame: "two passwords are not the same, please try again",
resetPasswordSuccess: "reset password success",
resetPasswordError: "reset password failed",
},
error: {

View File

@@ -38,6 +38,10 @@ export default {
sendCaptchaError: "验证码发送失败,请稍后再试!",
loginSuccess: "登录成功!",
loginError: "登录失败!",
twoPasswordNotSame: "两次密码输入不一致!",
resetPasswordSuccess: "密码重置成功!",
resetPasswordError: "密码重置失败!",
},

View File

@@ -14,7 +14,7 @@ class Request {
this.instance.interceptors.request.use(
(config) => {
const user = useStore().user;
const token: string | undefined = user.getUser()?.accessToken;
const token: string | undefined = user.user.accessToken;
if (token) {
config.headers.Authorization = `${import.meta.env.VITE_APP_TOKEN_KEY} ${token}`;
}

View File

@@ -15,7 +15,8 @@
>
<AFormItem name="phone">
<span class="forget-card-span">{{ t("login.phone") }}</span>
<AInput v-model:value="ResetPasswordForm.phone" :placeholder=phoneValidate size="large" allow-clear>
<AInput v-model:value="ResetPasswordForm.phone" :placeholder=phoneValidate size="large" allow-clear
autocomplete="off">
<template #prefix>
<TabletOutlined/>
</template>
@@ -26,7 +27,7 @@
<span class="forget-card-span">{{ t("login.phoneCaptcha") }}</span>
<AFlex :vertical="false" align="center" justify="center">
<AInput v-model:value="ResetPasswordForm.captcha" :placeholder=captchaValidate size="large"
allow-clear>
allow-clear autocomplete="off">
<template #prefix>
<SafetyOutlined/>
</template>
@@ -44,20 +45,21 @@
</AFormItem>
<AFormItem name="password">
<span class="forget-card-span">{{ t("login.password") }}</span>
<AInput v-model:value="ResetPasswordForm.password" :placeholder=passwordValidate size="large" allow-clear>
<AInputPassword v-model:value="ResetPasswordForm.password" :placeholder=passwordValidate size="large"
allow-clear autocomplete="password">
<template #prefix>
<LockOutlined/>
</template>
</AInput>
</AInputPassword>
</AFormItem>
<AFormItem name="repassword">
<span class="forget-card-span">{{ t("login.repassword") }}</span>
<AInput v-model:value="ResetPasswordForm.repassword" :placeholder=repasswordValidate size="large"
allow-clear>
<AInputPassword v-model:value="ResetPasswordForm.repassword" :placeholder=repasswordValidate size="large"
allow-clear autocomplete="password">
<template #prefix>
<LockOutlined/>
</template>
</AInput>
</AInputPassword>
</AFormItem>
<AFormItem>
<AButton @click="resetPasswordSubmit" style="width: 100%;" type="primary" size="large">{{
@@ -77,6 +79,18 @@
</ATooltip>
</ACard>
</div>
<div v-if="showRotateCaptcha" class="mask">
<!-- 滑动验证码 -->
<gocaptcha-rotate
class="gocaptcha-rotate"
v-if="showRotateCaptcha"
:data="captchaData"
:config="{
title: t('login.rotateCaptchaTitle'),
}"
:events="resetPasswordRotateEvent"
/>
</div>
</div>
</template>
<script setup lang="ts">
@@ -86,11 +100,27 @@ import {useRouter} from "vue-router";
import {onMounted, reactive, ref, UnwrapRef} from "vue";
import {ResetPassword} from "@/types/user";
import {Rule} from "ant-design-vue/lib/form";
import {checkRotatedCaptcha, getRotatedCaptchaData} from "@/api/captcha";
import {message} from "ant-design-vue";
import {resetPasswordApi, sendMessage} from "@/api/user";
const router = useRouter();
const {t} = useI18n();
const resetPasswordRef = ref();
const captchaData = reactive({angle: 0, image: "", thumb: "", key: ""});
const showRotateCaptcha = ref<boolean>(false);
const captchaErrorCount = ref<number>(0);
const resetPasswordRotateEvent = {
confirm: (angle: number) => {
checkPhoneLoginCaptcha(angle);
},
close: () => {
showRotateCaptcha.value = false;
},
refresh: () => {
getRotateCaptcha();
},
};
const ResetPasswordForm: UnwrapRef<ResetPassword> = reactive({
phone: '',
captcha: '',
@@ -102,34 +132,40 @@ const passwordValidate = ref<string>(t('login.passwordValidate'));
const phoneValidate = ref<string>(t('login.phoneValidate'));
const captchaValidate = ref<string>(t('login.captchaValidate'));
const repasswordValidate = ref<string>(t('login.repasswordValidate'));
/**
* 表单验证规则
* @param _rule
* @param value
*/
const validateRepassword = async (_rule: Rule, value: string) => {
if (value !== ResetPasswordForm.password) {
return Promise.reject(t('login.twoPasswordNotSame'));
} else {
return Promise.resolve();
}
};
// 表单验证规则
const rules: Record<string, Rule[]> = {
password: [
{
required: true, message: t('login.passwordValidate'), trigger: 'change'
required: true, message: t('login.passwordValidate'), trigger: 'blur'
},
{
pattern: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{6,18}$/,
message: t('login.passwordRule'),
trigger: 'blur'
},
],
repassword: [
{
required: true, message: t('login.repasswordValidate'), trigger: 'change'
},
{
pattern: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{6,18}$/,
message: t('login.passwordRule'),
},
{validator: validateRepassword, trigger: 'blur'}
],
phone: [
{required: true, message: t('login.phoneValidate'), trigger: 'change'},
{required: true, message: t('login.phoneValidate'), trigger: 'blur'},
{pattern: /^1[2-9]\d{9}$/, message: t('login.phoneValidate')}
],
captcha: [
{
required: true, message: t('login.captchaValidate'), trigger: 'change'
required: true, message: t('login.captchaValidate'), trigger: 'blur'
}
]
};
@@ -138,6 +174,7 @@ const state = reactive({
countDownTime: 60,
showCountDown: false,
});
/**
* 验证码发送倒计时
*/
@@ -185,8 +222,9 @@ async function sendCaptcha() {
resetPasswordRef.value
.validateFields("phone")
.then(() => {
countDown();
console.log('values');
getRotateCaptcha().then(() => {
showRotateCaptcha.value = true;
});
})
.catch((error: any) => {
console.log('error', error);
@@ -199,13 +237,80 @@ async function sendCaptcha() {
async function resetPasswordSubmit() {
resetPasswordRef.value
.validate()
.then(() => {
console.log('values', ResetPasswordForm);
.then(async () => {
const res: any = await resetPasswordApi(ResetPasswordForm);
if (res.code === 0 && res.success) {
message.success(t('login.resetPasswordSuccess'));
await router.push('/login');
} else {
message.error(t('login.resetPasswordError'));
}
})
.catch((error: any) => {
console.log('error', error);
});
}
/**
* 获取旋转验证码数据
*/
async function getRotateCaptcha() {
const data: any = await getRotatedCaptchaData();
if (data.code === 0 && data.data) {
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'));
}
}
/**
* 发送手机验证码
*/
async function sendMessageByPhone(): Promise<boolean> {
const phone: string = ResetPasswordForm.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;
}
}
/**
* 验证旋转验证码
* @param angle
*/
async function checkPhoneLoginCaptcha(angle: number) {
if (captchaErrorCount.value >= 2) {
message.error(t('login.captchaError'));
getRotateCaptcha().then(() => {
captchaErrorCount.value = 0;
});
} else {
const result: any = await checkRotatedCaptcha(angle, captchaData.key);
if (result.code === 0 && result.success) {
showRotateCaptcha.value = false;
const result: boolean = await sendMessageByPhone();
if (result) {
countDown();
}
} else if (result.code === 1011) {
message.error(t('login.captchaExpired'));
getRotateCaptcha().then(() => {
captchaErrorCount.value = 0;
});
} else {
captchaErrorCount.value++;
message.error(t('login.captchaError'));
}
}
}
</script>
<style src="./index.scss" scoped>
@import "@/assets/styles/global.scss";

View File

@@ -88,4 +88,23 @@
}
}
.gocaptcha-rotate {
width: 330px;
height: 350px;
position: absolute;
border-radius: 15px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.mask { /* 遮罩层的写法 */
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.5);
}
}

View File

@@ -134,23 +134,35 @@
</ATooltip>
</ACard>
</div>
<div v-if="showRotateCaptcha" class="mask">
<div v-if="showPhoneRotateCaptcha" class="mask">
<!-- 滑动验证码 -->
<gocaptcha-rotate
class="gocaptcha-rotate"
v-if="showRotateCaptcha"
v-if="showPhoneRotateCaptcha"
:data="captchaData"
:config="{
title: t('login.rotateCaptchaTitle'),
}"
:events="rotateEvent"
:events="phoneLoginRotateEvent"
/>
</div>
<div v-if="showAccountRotateCaptcha" class="mask">
<!-- 滑动验证码 -->
<gocaptcha-rotate
class="gocaptcha-rotate"
v-if="showAccountRotateCaptcha"
:data="captchaData"
:config="{
title: t('login.rotateCaptchaTitle'),
}"
:events="accountLoginRotateEvent"
/>
</div>
</div>
</template>
<script setup lang="ts">
import {Rule} from "ant-design-vue/lib/form";
import {onMounted, reactive, ref, UnwrapRef} from "vue";
import {onBeforeMount, onMounted, reactive, ref, UnwrapRef} from "vue";
import {AccountLogin, PhoneLogin} from "@/types/user";
import {useI18n} from "vue-i18n";
import BoxDog from "@/components/BoxDog/BoxDog.vue";
@@ -158,23 +170,35 @@ 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 {accountLoginApi, phoneLoginApi, sendMessage} from "@/api/user";
import useStore from "@/store";
const router = useRouter();
const {t} = useI18n();
const accountLoginFormRef = ref<any>();
const phoneLoginFormRef = ref<any>();
const showRotateCaptcha = ref<boolean>(false);
const showPhoneRotateCaptcha = ref<boolean>(false);
const showAccountRotateCaptcha = ref<boolean>(false);
const captchaData = reactive({angle: 0, image: "", thumb: "", key: ""});
const captchaErrorCount = ref<number>(0);
const autoLoginChecked = ref<boolean>(localStorage.getItem('auto_login') === 'true');
const rotateEvent = {
const phoneLoginRotateEvent = {
confirm: (angle: number) => {
checkCaptcha(angle);
checkPhoneLoginCaptcha(angle);
},
close: () => {
closeRotateCaptcha();
showPhoneRotateCaptcha.value = false;
},
refresh: () => {
getRotateCaptcha();
},
};
const accountLoginRotateEvent = {
confirm: (angle: number) => {
checkAccountLoginCaptcha(angle);
},
close: () => {
showAccountRotateCaptcha.value = false;
},
refresh: () => {
getRotateCaptcha();
@@ -271,7 +295,7 @@ async function sendCaptcha() {
.validateFields("phone")
.then(() => {
getRotateCaptcha().then(() => {
showRotateCaptcha.value = true;
showPhoneRotateCaptcha.value = true;
});
})
.catch((error: any) => {
@@ -286,7 +310,9 @@ async function accountLoginSubmit() {
accountLoginFormRef.value
.validate()
.then(() => {
console.log('values', accountLoginForm);
getRotateCaptcha().then(() => {
showAccountRotateCaptcha.value = true;
});
})
.catch((error: any) => {
console.log('error', error);
@@ -338,7 +364,7 @@ async function getRotateCaptcha() {
* 验证旋转验证码
* @param angle
*/
async function checkCaptcha(angle: number) {
async function checkPhoneLoginCaptcha(angle: number) {
if (captchaErrorCount.value >= 2) {
message.error(t('login.captchaError'));
getRotateCaptcha().then(() => {
@@ -347,7 +373,7 @@ async function checkCaptcha(angle: number) {
} else {
const result: any = await checkRotatedCaptcha(angle, captchaData.key);
if (result.code === 0 && result.success) {
showRotateCaptcha.value = false;
showPhoneRotateCaptcha.value = false;
const result: boolean = await sendMessageByPhone();
if (result) {
countDown();
@@ -357,7 +383,6 @@ async function checkCaptcha(angle: number) {
getRotateCaptcha().then(() => {
captchaErrorCount.value = 0;
});
} else {
captchaErrorCount.value++;
message.error(t('login.captchaError'));
@@ -366,10 +391,41 @@ async function checkCaptcha(angle: number) {
}
/**
* 关闭旋转验证码
* 检查账户登录旋转验证码
* @param angle
*/
async function closeRotateCaptcha() {
showRotateCaptcha.value = false;
async function checkAccountLoginCaptcha(angle: number) {
if (captchaErrorCount.value >= 2) {
message.error(t('login.captchaError'));
getRotateCaptcha().then(() => {
captchaErrorCount.value = 0;
});
} else {
const result: any = await checkRotatedCaptcha(angle, captchaData.key);
if (result.code === 0 && result.success) {
showAccountRotateCaptcha.value = false;
const res: any = await accountLoginApi(accountLoginForm);
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(t('login.loginError'));
}
} else if (result.code === 1011) {
message.error(t('login.captchaExpired'));
getRotateCaptcha().then(() => {
captchaErrorCount.value = 0;
});
} else {
captchaErrorCount.value++;
message.error(t('login.captchaError'));
}
}
}
/**
@@ -387,9 +443,20 @@ async function sendMessageByPhone(): Promise<boolean> {
}
}
/**
* 自动登录
* @param checkedValue
*/
async function autoLoginChang(checkedValue: boolean) {
return localStorage.setItem('auto_login', String(checkedValue));
}
onBeforeMount(() => {
const autoLogin: string | null = localStorage.getItem('auto_login');
if (!autoLogin) {
localStorage.setItem('auto_login', 'true');
}
});
</script>
<style src="./index.scss" scoped>
@import "@/assets/styles/global.scss";

View File

@@ -17,8 +17,10 @@
class="qrlogin-card-qr"
:size="230"
:error-level="'H'"
value="https://www.antdv.com"
icon="https://www.antdv.com/assets/logo.1ef800a8.svg"
:status="status"
@refresh="async () => await getQrCode()"
:value=qrcode
:icon="logo"
/>
<ACheckbox class="qrlogin-card-auto-login">{{ t("login.autoLogin") }}</ACheckbox>
</AFlex>
@@ -40,11 +42,49 @@ import {useI18n} from "vue-i18n";
import BoxDog from "@/components/BoxDog/BoxDog.vue";
import QRLoginFooter from "@/views/QRLogin/QRLoginFooter.vue";
import {useRouter} from 'vue-router';
import {generateClientId, generateQrCode} from "@/api/oauth";
import {ref} from "vue";
import logo from "@/assets/svgs/logo-schisandra.svg";
const {t} = useI18n();
const router = useRouter();
const qrcode = ref<string>('');
const status = ref<string>('loading');
/**
* 获取client_id
*/
async function getClientId() {
const id: string | null = localStorage.getItem('client_id');
if (!id) {
const res: any = await generateClientId();
if (res.code === 0 && res.data) {
localStorage.setItem('client_id', res.data);
}
}
}
getClientId();
/**
* 获取二维码
*/
async function getQrCode() {
const clientId: any = localStorage.getItem('client_id');
const res: any = await generateQrCode(clientId);
console.log(res);
if (res.code === 0 && res.data) {
status.value = 'active';
qrcode.value = res.data;
localStorage.setItem('qr_code', res.data);
} else {
status.value = 'expired';
}
}
getQrCode();
</script>
<style src="./index.scss" scoped>
@import "@/assets/styles/global.scss";

View File

@@ -15,7 +15,7 @@
import {GithubOutlined, QqOutlined, TabletOutlined} from "@ant-design/icons-vue";
import {useI18n} from "vue-i18n";
import {h} from "vue";
import {useRouter} from 'vue-router'
import {useRouter} from 'vue-router';
const router = useRouter();
const {t} = useI18n();