22 changed files with 639 additions and 64 deletions
@ -0,0 +1,47 @@ |
|||
import type { |
|||
VbenFormSchema as FormSchema, |
|||
VbenFormProps, |
|||
} from '@vben/common-ui'; |
|||
|
|||
import type { ComponentType } from './component'; |
|||
|
|||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
setupVbenForm<ComponentType>({ |
|||
config: { |
|||
// ant design vue组件库默认都是 v-model:value
|
|||
baseModelPropName: 'value', |
|||
|
|||
// 一些组件是 v-model:checked 或者 v-model:fileList
|
|||
modelPropNameMap: { |
|||
Checkbox: 'checked', |
|||
Radio: 'checked', |
|||
Switch: 'checked', |
|||
Upload: 'fileList', |
|||
}, |
|||
}, |
|||
defineRules: { |
|||
// 输入项目必填国际化适配
|
|||
required: (value, _params, ctx) => { |
|||
if (value === undefined || value === null || value.length === 0) { |
|||
return $t('ui.formRules.required', [ctx.label]); |
|||
} |
|||
return true; |
|||
}, |
|||
// 选择项目必填国际化适配
|
|||
selectRequired: (value, _params, ctx) => { |
|||
if (value === undefined || value === null) { |
|||
return $t('ui.formRules.selectRequired', [ctx.label]); |
|||
} |
|||
return true; |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
const useVbenForm = useForm<ComponentType>; |
|||
|
|||
export { useVbenForm, z }; |
|||
|
|||
export type VbenFormSchema = FormSchema<ComponentType>; |
|||
export type { VbenFormProps }; |
|||
@ -0,0 +1,206 @@ |
|||
<script setup lang="ts"> |
|||
import type { TwoFactorProvider } from '@abp/account'; |
|||
import type { FormExpose } from 'ant-design-vue/es/form/Form'; |
|||
|
|||
import { computed, ref, toValue, useTemplateRef } from 'vue'; |
|||
|
|||
import { useVbenModal } from '@vben/common-ui'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { useAccountApi } from '@abp/account'; |
|||
import { Button, Form, Input, Select } from 'ant-design-vue'; |
|||
|
|||
import { useAuthStore } from '#/store/auth'; |
|||
|
|||
const FormItem = Form.Item; |
|||
|
|||
interface TwoFactorState { |
|||
username: string; |
|||
password: string; |
|||
twoFactorToken: string; |
|||
userId: string; |
|||
twoFactorProvider: string; |
|||
code: string; |
|||
email: string; |
|||
phoneNumber: string; |
|||
} |
|||
|
|||
const formModel = ref({} as TwoFactorState); |
|||
const form = useTemplateRef<FormExpose>('formRef'); |
|||
const twoFactorProviders = ref<TwoFactorProvider[]>([]); |
|||
const sendValidCodeLoading = ref(false); |
|||
const sendValidCodeInternal = ref(0); |
|||
const getSendValidCodeLoading = computed(() => { |
|||
return sendValidCodeInternal.value > 0; |
|||
}); |
|||
const getSendValidCodeTitle = computed(() => { |
|||
if (sendValidCodeInternal.value > 0) { |
|||
return `${sendValidCodeInternal.value}`; |
|||
} |
|||
return $t('abp.oauth.twoFactor.getCode'); |
|||
}); |
|||
|
|||
const authStore = useAuthStore(); |
|||
const { |
|||
getTwoFactorProvidersApi, |
|||
sendEmailSigninCodeApi, |
|||
sendPhoneSigninCodeApi, |
|||
} = useAccountApi(); |
|||
|
|||
const [Modal, modalApi] = useVbenModal({ |
|||
title: $t('abp.oauth.twoFactor.title'), |
|||
fullscreenButton: false, |
|||
closeOnClickModal: false, |
|||
closeOnPressEscape: false, |
|||
onConfirm: onLogin, |
|||
async onOpenChange(isOpen) { |
|||
if (isOpen) { |
|||
const state = modalApi.getData<TwoFactorState>(); |
|||
formModel.value = state; |
|||
const { items } = await getTwoFactorProvidersApi({ |
|||
userId: state.userId, |
|||
}); |
|||
twoFactorProviders.value = items; |
|||
} |
|||
}, |
|||
}); |
|||
/** 二次认证登陆 */ |
|||
async function onLogin() { |
|||
modalApi.setState({ |
|||
confirmLoading: true, |
|||
closable: false, |
|||
}); |
|||
try { |
|||
await form.value?.validate(); |
|||
const model = toValue(formModel); |
|||
await authStore.authLogin({ |
|||
username: model.username, |
|||
password: model.password, |
|||
TwoFactorProvider: model.twoFactorProvider, |
|||
TwoFactorCode: model.code, |
|||
}); |
|||
} finally { |
|||
modalApi.setState({ |
|||
confirmLoading: false, |
|||
closable: true, |
|||
}); |
|||
} |
|||
} |
|||
/** 发送邮件验证代码 */ |
|||
async function onSendEmail() { |
|||
await form.value?.validateFields('email'); |
|||
await onSendCode(() => |
|||
sendEmailSigninCodeApi({ |
|||
emailAddress: formModel.value.email, |
|||
}), |
|||
); |
|||
} |
|||
/** 发送短信验证代码 */ |
|||
async function onSendSms() { |
|||
await form.value?.validateFields('phoneNumber'); |
|||
await onSendCode(() => |
|||
sendPhoneSigninCodeApi({ |
|||
phoneNumber: formModel.value.phoneNumber, |
|||
}), |
|||
); |
|||
} |
|||
/** 发送验证代码 */ |
|||
async function onSendCode(api: () => Promise<void>) { |
|||
try { |
|||
sendValidCodeLoading.value = true; |
|||
sendValidCodeInternal.value = 60; |
|||
await api(); |
|||
setInterval(() => { |
|||
if (sendValidCodeInternal.value <= 0) { |
|||
return; |
|||
} |
|||
sendValidCodeInternal.value -= 1; |
|||
}, 1000); |
|||
} catch { |
|||
sendValidCodeInternal.value = 0; |
|||
} finally { |
|||
sendValidCodeLoading.value = false; |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Modal> |
|||
<Form |
|||
ref="formRef" |
|||
:label-col="{ span: 6 }" |
|||
:model="formModel" |
|||
:wrapper-col="{ span: 18 }" |
|||
> |
|||
<!-- 选择验证器 --> |
|||
<FormItem |
|||
:label="$t('abp.oauth.twoFactor.authenticator')" |
|||
name="twoFactorProvider" |
|||
required |
|||
> |
|||
<Select |
|||
v-model:value="formModel.twoFactorProvider" |
|||
:field-names="{ label: 'name', value: 'value' }" |
|||
:options="twoFactorProviders" |
|||
allow-clear |
|||
/> |
|||
</FormItem> |
|||
<!-- 邮件验证 --> |
|||
<template v-if="formModel.twoFactorProvider === 'Email'"> |
|||
<FormItem |
|||
:label="$t('abp.oauth.twoFactor.emailAddress')" |
|||
name="email" |
|||
required |
|||
> |
|||
<Input |
|||
v-model:value="formModel.email" |
|||
autocomplete="off" |
|||
type="email" |
|||
/> |
|||
</FormItem> |
|||
<FormItem :label="$t('abp.oauth.twoFactor.code')" name="code" required> |
|||
<div class="flex flex-row gap-4"> |
|||
<Input v-model:value="formModel.code" autocomplete="off" /> |
|||
<Button |
|||
:disabled="getSendValidCodeLoading" |
|||
:loading="sendValidCodeLoading" |
|||
@click="onSendEmail" |
|||
> |
|||
{{ getSendValidCodeTitle }} |
|||
</Button> |
|||
</div> |
|||
</FormItem> |
|||
</template> |
|||
<!-- 手机号码验证 --> |
|||
<template v-if="formModel.twoFactorProvider === 'Phone'"> |
|||
<FormItem |
|||
:label="$t('abp.oauth.twoFactor.phoneNumber')" |
|||
name="phoneNumber" |
|||
required |
|||
> |
|||
<Input v-model:value="formModel.phoneNumber" autocomplete="off" /> |
|||
</FormItem> |
|||
<FormItem :label="$t('abp.oauth.twoFactor.code')" name="code" required> |
|||
<div class="flex flex-row gap-4"> |
|||
<Input v-model:value="formModel.code" autocomplete="off" /> |
|||
<Button |
|||
:disabled="getSendValidCodeLoading" |
|||
:loading="sendValidCodeLoading" |
|||
@click="onSendSms" |
|||
> |
|||
{{ getSendValidCodeTitle }} |
|||
</Button> |
|||
</div> |
|||
</FormItem> |
|||
</template> |
|||
<!-- 身份验证程序验证 --> |
|||
<template v-else-if="formModel.twoFactorProvider === 'Authenticator'"> |
|||
<FormItem :label="$t('abp.oauth.twoFactor.code')" name="code" required> |
|||
<Input v-model:value="formModel.code" autocomplete="off" /> |
|||
</FormItem> |
|||
</template> |
|||
</Form> |
|||
</Modal> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -1,3 +1,4 @@ |
|||
export { useAccountApi } from './useAccountApi'; |
|||
export { useProfileApi } from './useProfileApi'; |
|||
export { useTokenApi } from './useTokenApi'; |
|||
export { useUserInfoApi } from './useUserInfoApi'; |
|||
|
|||
@ -0,0 +1,63 @@ |
|||
import type { ListResultDto } from '@abp/core'; |
|||
|
|||
import type { |
|||
GetTwoFactorProvidersInput, |
|||
SendEmailSigninCodeDto, |
|||
SendPhoneSigninCodeDto, |
|||
TwoFactorProvider, |
|||
} from '../types/account'; |
|||
|
|||
import { useRequest } from '@abp/request'; |
|||
|
|||
export function useAccountApi() { |
|||
const { cancel, request } = useRequest(); |
|||
|
|||
/** |
|||
* 获取可用的二次认证验证器 |
|||
* @param input 参数 |
|||
*/ |
|||
function getTwoFactorProvidersApi( |
|||
input: GetTwoFactorProvidersInput, |
|||
): Promise<ListResultDto<TwoFactorProvider>> { |
|||
return request<ListResultDto<TwoFactorProvider>>( |
|||
'/api/account/two-factor-providers', |
|||
{ |
|||
method: 'GET', |
|||
params: input, |
|||
}, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 发送登陆验证邮件 |
|||
* @param input 参数 |
|||
*/ |
|||
function sendEmailSigninCodeApi( |
|||
input: SendEmailSigninCodeDto, |
|||
): Promise<void> { |
|||
return request('/api/account/email/send-signin-code', { |
|||
data: input, |
|||
method: 'POST', |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 发送登陆验证短信 |
|||
* @param input 参数 |
|||
*/ |
|||
function sendPhoneSigninCodeApi( |
|||
input: SendPhoneSigninCodeDto, |
|||
): Promise<void> { |
|||
return request('/api/account/phone/send-signin-code', { |
|||
data: input, |
|||
method: 'POST', |
|||
}); |
|||
} |
|||
|
|||
return { |
|||
cancel, |
|||
getTwoFactorProvidersApi, |
|||
sendEmailSigninCodeApi, |
|||
sendPhoneSigninCodeApi, |
|||
}; |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
<script setup lang="ts"> |
|||
import { ref } from 'vue'; |
|||
|
|||
import { useVbenModal } from '@vben/common-ui'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { Form, Input, message } from 'ant-design-vue'; |
|||
|
|||
import { useProfileApi } from '../../api/useProfileApi'; |
|||
|
|||
const FormItem = Form.Item; |
|||
|
|||
interface State { |
|||
confirmToken: string; |
|||
email: string; |
|||
returnUrl?: string; |
|||
userId: string; |
|||
} |
|||
const formModel = ref({} as State); |
|||
|
|||
const { cancel, confirmEmailApi } = useProfileApi(); |
|||
const [Modal, modalApi] = useVbenModal({ |
|||
onClosed: cancel, |
|||
onConfirm: onSubmit, |
|||
onOpenChange(isOpen) { |
|||
if (isOpen) { |
|||
const state = modalApi.getData<State>(); |
|||
formModel.value = state; |
|||
} |
|||
}, |
|||
title: $t('AbpAccount.EmailConfirm'), |
|||
}); |
|||
async function onSubmit() { |
|||
try { |
|||
modalApi.setState({ confirmLoading: true }); |
|||
await confirmEmailApi({ |
|||
confirmToken: encodeURIComponent(formModel.value.confirmToken), |
|||
}); |
|||
message.success($t('AbpAccount.YourEmailIsSuccessfullyConfirm')); |
|||
modalApi.close(); |
|||
if (formModel.value.returnUrl) { |
|||
window.location.href = formModel.value.returnUrl; |
|||
} |
|||
} finally { |
|||
modalApi.setState({ confirmLoading: false }); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Modal> |
|||
<Form :model="formModel"> |
|||
<FormItem :label="$t('AbpAccount.DisplayName:Email')"> |
|||
<Input v-model:value="formModel.email" readonly /> |
|||
</FormItem> |
|||
</Form> |
|||
</Modal> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,22 @@ |
|||
import type { NameValue } from '@abp/core'; |
|||
|
|||
interface GetTwoFactorProvidersInput { |
|||
userId: string; |
|||
} |
|||
|
|||
interface SendEmailSigninCodeDto { |
|||
emailAddress: string; |
|||
} |
|||
|
|||
interface SendPhoneSigninCodeDto { |
|||
phoneNumber: string; |
|||
} |
|||
|
|||
type TwoFactorProvider = NameValue<string>; |
|||
|
|||
export type { |
|||
GetTwoFactorProvidersInput, |
|||
SendEmailSigninCodeDto, |
|||
SendPhoneSigninCodeDto, |
|||
TwoFactorProvider, |
|||
}; |
|||
@ -1,3 +1,4 @@ |
|||
export * from './account'; |
|||
export * from './profile'; |
|||
export * from './token'; |
|||
export * from './user'; |
|||
|
|||
Loading…
Reference in new issue