Browse Source

feat(vben5): Realize mobile phone number login

- 新增手机号码登录api
- 新增发送手机登录验证码api
- 新增发送手机重置密码验证码api
pull/1219/head
colin 1 year ago
parent
commit
f77c96cdc2
  1. 31
      apps/vben5/apps/app-antd/src/store/auth.ts
  2. 42
      apps/vben5/apps/app-antd/src/views/_core/authentication/code-login.vue
  3. 1
      apps/vben5/packages/@abp/account/src/api/index.ts
  4. 28
      apps/vben5/packages/@abp/account/src/api/useAccountApi.ts
  5. 46
      apps/vben5/packages/@abp/account/src/api/usePhoneLoginApi.ts
  6. 2
      apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts
  7. 12
      apps/vben5/packages/@abp/account/src/types/account.ts
  8. 8
      apps/vben5/packages/@abp/account/src/types/token.ts

31
apps/vben5/apps/app-antd/src/store/auth.ts

@ -2,13 +2,14 @@ import type { TokenResult } from '@abp/account';
import type { Recordable, UserInfo } from '@vben/types'; import type { Recordable, UserInfo } from '@vben/types';
import { ref } from 'vue'; import { ref, watch } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants'; import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
import { import {
usePhoneLoginApi,
useProfileApi, useProfileApi,
useQrCodeLoginApi, useQrCodeLoginApi,
useTokenApi, useTokenApi,
@ -25,6 +26,7 @@ export const useAuthStore = defineStore('auth', () => {
const { publish } = useEventBus(); const { publish } = useEventBus();
const { loginApi } = useTokenApi(); const { loginApi } = useTokenApi();
const { loginApi: qrcodeLoginApi } = useQrCodeLoginApi(); const { loginApi: qrcodeLoginApi } = useQrCodeLoginApi();
const { loginApi: phoneLoginApi } = usePhoneLoginApi();
const { getUserInfoApi } = useUserInfoApi(); const { getUserInfoApi } = useUserInfoApi();
const { getConfigApi } = useAbpConfigApi(); const { getConfigApi } = useAbpConfigApi();
const { getPictureApi } = useProfileApi(); const { getPictureApi } = useProfileApi();
@ -35,6 +37,17 @@ export const useAuthStore = defineStore('auth', () => {
const loginLoading = ref(false); const loginLoading = ref(false);
watch(
() => accessStore.accessToken,
(accessToken) => {
if (accessToken && !loginLoading.value) {
loginLoading.value = true;
fetchUserInfo();
loginLoading.value = false;
}
},
);
async function qrcodeLogin( async function qrcodeLogin(
key: string, key: string,
tenantId?: string, tenantId?: string,
@ -49,6 +62,20 @@ export const useAuthStore = defineStore('auth', () => {
} }
} }
async function phoneLogin(
phoneNumber: string,
code: string,
onSuccess?: () => Promise<void> | void,
) {
try {
loginLoading.value = true;
const result = await phoneLoginApi({ phoneNumber, code });
return await _loginSuccess(result, onSuccess);
} finally {
loginLoading.value = false;
}
}
/** /**
* 异步处理登录操作 * 异步处理登录操作
* Asynchronously handle the login process * Asynchronously handle the login process
@ -126,7 +153,6 @@ export const useAuthStore = defineStore('auth', () => {
) { ) {
// 异步处理用户登录操作并获取 accessToken // 异步处理用户登录操作并获取 accessToken
let userInfo: null | UserInfo = null; let userInfo: null | UserInfo = null;
loginLoading.value = true;
const { accessToken, tokenType, refreshToken } = loginResult; const { accessToken, tokenType, refreshToken } = loginResult;
// 如果成功获取到 accessToken // 如果成功获取到 accessToken
if (accessToken) { if (accessToken) {
@ -172,6 +198,7 @@ export const useAuthStore = defineStore('auth', () => {
return { return {
$reset, $reset,
authLogin, authLogin,
phoneLogin,
qrcodeLogin, qrcodeLogin,
fetchUserInfo, fetchUserInfo,
loginLoading, loginLoading,

42
apps/vben5/apps/app-antd/src/views/_core/authentication/code-login.vue

@ -1,29 +1,42 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VbenFormSchema } from '@vben/common-ui'; import type { ExtendedFormApi, VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types'; import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue'; import { computed, ref, useTemplateRef } from 'vue';
import { AuthenticationCodeLogin, z } from '@vben/common-ui'; import { AuthenticationCodeLogin, z } from '@vben/common-ui';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { useAccountApi } from '@abp/account';
import { isPhone } from '@abp/core';
import { useAuthStore } from '#/store/auth';
interface CodeLoginExpose {
getFormApi(): ExtendedFormApi;
}
defineOptions({ name: 'CodeLogin' }); defineOptions({ name: 'CodeLogin' });
const authStore = useAuthStore();
const { sendPhoneSigninCodeApi } = useAccountApi();
const loading = ref(false); const loading = ref(false);
const codeLogin = useTemplateRef<CodeLoginExpose>('codeLogin');
const formSchema = computed((): VbenFormSchema[] => { const formSchema = computed((): VbenFormSchema[] => {
return [ return [
{ {
component: 'VbenInput', component: 'Input',
componentProps: { componentProps: {
placeholder: $t('authentication.mobile'), placeholder: $t('AbpAccount.DisplayName:PhoneNumber'),
}, },
fieldName: 'phoneNumber', fieldName: 'phoneNumber',
label: $t('authentication.mobile'), label: $t('AbpAccount.DisplayName:PhoneNumber'),
rules: z rules: z
.string() .string()
.min(1, { message: $t('authentication.mobileTip') }) .min(1, { message: $t('authentication.mobileTip') })
.refine((v) => /^\d{11}$/.test(v), { .refine((v) => isPhone(v), {
message: $t('authentication.mobileErrortip'), message: $t('authentication.mobileErrortip'),
}), }),
}, },
@ -37,6 +50,7 @@ const formSchema = computed((): VbenFormSchema[] => {
: $t('authentication.sendCode'); : $t('authentication.sendCode');
return text; return text;
}, },
handleSendCode: onSendCode,
placeholder: $t('authentication.code'), placeholder: $t('authentication.code'),
}, },
fieldName: 'code', fieldName: 'code',
@ -45,19 +59,31 @@ const formSchema = computed((): VbenFormSchema[] => {
}, },
]; ];
}); });
async function onSendCode() {
const formApi = codeLogin.value?.getFormApi();
const input = await formApi?.getValues();
await sendPhoneSigninCodeApi({
phoneNumber: input!.phoneNumber,
});
}
/** /**
* 异步处理登录操作 * 异步处理登录操作
* Asynchronously handle the login process * Asynchronously handle the login process
* @param values 登录表单数据 * @param values 登录表单数据
*/ */
async function handleLogin(values: Recordable<any>) { async function handleLogin(values: Recordable<any>) {
// eslint-disable-next-line no-console try {
console.log(values); loading.value = true;
await authStore.phoneLogin(values.phoneNumber, values.code);
} finally {
loading.value = false;
}
} }
</script> </script>
<template> <template>
<AuthenticationCodeLogin <AuthenticationCodeLogin
ref="codeLogin"
:form-schema="formSchema" :form-schema="formSchema"
:loading="loading" :loading="loading"
@submit="handleLogin" @submit="handleLogin"

1
apps/vben5/packages/@abp/account/src/api/index.ts

@ -1,5 +1,6 @@
export { useAccountApi } from './useAccountApi'; export { useAccountApi } from './useAccountApi';
export { useMySessionApi } from './useMySessionApi'; export { useMySessionApi } from './useMySessionApi';
export { usePhoneLoginApi } from './usePhoneLoginApi';
export { useProfileApi } from './useProfileApi'; export { useProfileApi } from './useProfileApi';
export { useQrCodeLoginApi } from './useQrCodeLoginApi'; export { useQrCodeLoginApi } from './useQrCodeLoginApi';
export { useTokenApi } from './useTokenApi'; export { useTokenApi } from './useTokenApi';

28
apps/vben5/packages/@abp/account/src/api/useAccountApi.ts

@ -2,7 +2,9 @@ import type { ListResultDto } from '@abp/core';
import type { import type {
GetTwoFactorProvidersInput, GetTwoFactorProvidersInput,
PhoneResetPasswordDto,
SendEmailSigninCodeDto, SendEmailSigninCodeDto,
SendPhoneResetPasswordCodeDto,
SendPhoneSigninCodeDto, SendPhoneSigninCodeDto,
TwoFactorProvider, TwoFactorProvider,
} from '../types/account'; } from '../types/account';
@ -54,10 +56,36 @@ export function useAccountApi() {
}); });
} }
/**
* 发送重置密码验证短信
* @param input 参数
*/
function sendPhoneResetPasswordCodeApi(
input: SendPhoneResetPasswordCodeDto,
): Promise<void> {
return request('/api/account/phone/send-password-reset-code', {
data: input,
method: 'POST',
});
}
/**
* 重置密码
* @param input 参数
*/
function resetPasswordApi(input: PhoneResetPasswordDto): Promise<void> {
return request('/api/account/phone/reset-password', {
data: input,
method: 'PUT',
});
}
return { return {
cancel, cancel,
getTwoFactorProvidersApi, getTwoFactorProvidersApi,
resetPasswordApi,
sendEmailSigninCodeApi, sendEmailSigninCodeApi,
sendPhoneResetPasswordCodeApi,
sendPhoneSigninCodeApi, sendPhoneSigninCodeApi,
}; };
} }

46
apps/vben5/packages/@abp/account/src/api/usePhoneLoginApi.ts

@ -0,0 +1,46 @@
import type { OAuthTokenResult, PhoneNumberTokenRequest } from '../types/token';
import { useAppConfig } from '@vben/hooks';
import { useRequest } from '@abp/request';
export function usePhoneLoginApi() {
const { cancel, request } = useRequest();
/**
* 手机验证码登录
* @param input 登录参数
* @returns 用户token
*/
async function loginApi(input: PhoneNumberTokenRequest) {
const { audience, clientId, clientSecret } = useAppConfig(
import.meta.env,
import.meta.env.PROD,
);
const result = await request<OAuthTokenResult>('/connect/token', {
data: {
client_id: clientId,
client_secret: clientSecret,
grant_type: 'phone_verify',
phone_number: input.phoneNumber,
phone_verify_code: input.code,
scope: audience,
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
method: 'POST',
});
return {
accessToken: result.access_token,
expiresIn: result.expires_in,
refreshToken: result.refresh_token,
tokenType: result.token_type,
};
}
return {
cancel,
loginApi,
};
}

2
apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts

@ -34,7 +34,7 @@ export function useQrCodeLoginApi() {
/** /**
* 二维码登录 * 二维码登录
* @param key 二维码Key * @param input 登录参数
* @returns 用户token * @returns 用户token
*/ */
async function loginApi(input: QrCodeTokenRequest) { async function loginApi(input: QrCodeTokenRequest) {

12
apps/vben5/packages/@abp/account/src/types/account.ts

@ -12,11 +12,23 @@ interface SendPhoneSigninCodeDto {
phoneNumber: string; phoneNumber: string;
} }
interface SendPhoneResetPasswordCodeDto {
phoneNumber: string;
}
interface PhoneResetPasswordDto {
code: string;
newPassword: string;
phoneNumber: string;
}
type TwoFactorProvider = NameValue<string>; type TwoFactorProvider = NameValue<string>;
export type { export type {
GetTwoFactorProvidersInput, GetTwoFactorProvidersInput,
PhoneResetPasswordDto,
SendEmailSigninCodeDto, SendEmailSigninCodeDto,
SendPhoneResetPasswordCodeDto,
SendPhoneSigninCodeDto, SendPhoneSigninCodeDto,
TwoFactorProvider, TwoFactorProvider,
}; };

8
apps/vben5/packages/@abp/account/src/types/token.ts

@ -14,6 +14,13 @@ interface PasswordTokenRequest extends TokenRequest {
/** 用户名 */ /** 用户名 */
userName: string; userName: string;
} }
/** 手机号授权请求数据模型 */
interface PhoneNumberTokenRequest {
/** 验证码 */
code: string;
/** 手机号 */
phoneNumber: string;
}
/** 扫码登录授权请求数据模型 */ /** 扫码登录授权请求数据模型 */
interface QrCodeTokenRequest { interface QrCodeTokenRequest {
/** 二维码Key */ /** 二维码Key */
@ -80,6 +87,7 @@ export type {
OAuthTokenResult, OAuthTokenResult,
PasswordTokenRequest, PasswordTokenRequest,
PasswordTokenRequestModel, PasswordTokenRequestModel,
PhoneNumberTokenRequest,
QrCodeTokenRequest, QrCodeTokenRequest,
ShouldChangePasswordError, ShouldChangePasswordError,
TokenRequest, TokenRequest,

Loading…
Cancel
Save