feat:增加 forget-password.vue 重置密码功能

pull/69/head^2
YunaiV 2025-04-08 22:04:56 +08:00
parent 446cb6cbbf
commit 2c105a21aa
5 changed files with 199 additions and 13 deletions

View File

@ -37,11 +37,18 @@ export namespace AuthApi {
/** 注册接口参数 */
export interface RegisterParams {
tenantName: string
username: string
password: string
captchaVerification: string
}
/** 重置密码接口参数 */
export interface ResetPasswordParams {
password: string;
mobile: string;
code: string;
}
}
/** 登录 */
@ -106,3 +113,8 @@ export const smsLogin = (data: AuthApi.SmsLoginParams) => {
export const register = (data: AuthApi.RegisterParams) => {
return requestClient.post('/system/auth/register', data)
}
/** 通过短信重置密码 */
export const smsResetPassword = (data: AuthApi.ResetPasswordParams) => {
return requestClient.post('/system/auth/reset-password', data)
}

View File

@ -2,40 +2,212 @@
import type { VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue';
import { computed, ref, onMounted, h } from 'vue';
import { AuthenticationForgetPassword, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { type AuthApi, sendSmsCode, smsResetPassword } from '#/api';
import { useAppConfig } from '@vben/hooks';
import { message } from 'ant-design-vue';
import { useRouter } from 'vue-router';
import { getTenantSimpleList, getTenantByWebsite } from '#/api/core/auth';
import { useAccessStore } from '@vben/stores';
defineOptions({ name: 'ForgetPassword' });
const { tenantEnable } = useAppConfig(import.meta.env, import.meta.env.PROD);
const accessStore = useAccessStore();
const router = useRouter();
const loading = ref(false);
const CODE_LENGTH = 4;
const forgetPasswordRef = ref();
/** 获取租户列表,并默认选中 */
const tenantList = ref<AuthApi.TenantResult[]>([]); //
const fetchTenantList = async () => {
if (!tenantEnable) {
return;
}
try {
//
const websiteTenantPromise = getTenantByWebsite(window.location.hostname);
tenantList.value = await getTenantSimpleList();
// > store >
let tenantId: number | null = null;
const websiteTenant = await websiteTenantPromise;
if (websiteTenant?.id) {
tenantId = websiteTenant.id;
}
// store
if (!tenantId && accessStore.tenantId) {
tenantId = accessStore.tenantId;
}
// 使
if (!tenantId && tenantList.value?.[0]?.id) {
tenantId = tenantList.value[0].id;
}
//
accessStore.setTenantId(tenantId);
forgetPasswordRef.value.getFormApi().setFieldValue('tenantId', tenantId);
} catch (error) {
console.error('获取租户列表失败:', error);
}
};
/** 组件挂载时获取租户信息 */
onMounted(() => {
fetchTenantList();
});
const formSchema = computed((): VbenFormSchema[] => {
return [
{
component: 'VbenSelect',
componentProps: {
options: tenantList.value.map((item) => ({
label: item.name,
value: item.id,
})),
placeholder: $t('authentication.tenantTip'),
},
fieldName: 'tenantId',
label: $t('authentication.tenant'),
rules: z
.number()
.nullable()
.refine((val) => val != null && val > 0, $t('authentication.tenantTip'))
.default(null),
dependencies: {
triggerFields: ['tenantId'],
if: tenantEnable,
trigger(values) {
if (values.tenantId) {
accessStore.setTenantId(values.tenantId);
}
},
},
},
{
component: 'VbenInput',
componentProps: {
placeholder: 'example@example.com',
placeholder: $t('authentication.mobile'),
},
fieldName: 'email',
label: $t('authentication.email'),
fieldName: 'mobile',
label: $t('authentication.mobile'),
rules: z
.string()
.min(1, { message: $t('authentication.emailTip') })
.email($t('authentication.emailValidErrorTip')),
.min(1, { message: $t('authentication.mobileTip') })
.refine((v) => /^\d{11}$/.test(v), {
message: $t('authentication.mobileErrortip'),
}),
},
{
component: 'VbenPinInput',
componentProps: {
codeLength: CODE_LENGTH,
createText: (countdown: number) => {
const text =
countdown > 0
? $t('authentication.sendText', [countdown])
: $t('authentication.sendCode');
return text;
},
placeholder: $t('authentication.code'),
handleSendCode: async () => {
loading.value = true;
try {
const formApi = forgetPasswordRef.value?.getFormApi();
if (!formApi) {
throw new Error('表单未准备好');
}
//
await formApi.validateField('mobile');
const isMobileValid = await formApi.isFieldValid('mobile');
if (!isMobileValid) {
throw new Error('请输入有效的手机号码');
}
//
const { mobile } = await formApi.getValues();
const scene = 23; //
await sendSmsCode({ mobile, scene });
message.success('验证码发送成功');
} finally {
loading.value = false;
}
}
},
fieldName: 'code',
label: $t('authentication.code'),
rules: z.string().length(CODE_LENGTH, {
message: $t('authentication.codeTip', [CODE_LENGTH]),
}),
},
{
component: 'VbenInputPassword',
componentProps: {
passwordStrength: true,
placeholder: $t('authentication.password'),
},
fieldName: 'password',
label: $t('authentication.password'),
renderComponentContent() {
return {
strengthText: () => $t('authentication.passwordStrength'),
};
},
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
},
{
component: 'VbenInputPassword',
componentProps: {
placeholder: $t('authentication.confirmPassword'),
},
dependencies: {
rules(values) {
const { password } = values;
return z
.string({ required_error: $t('authentication.passwordTip') })
.min(1, { message: $t('authentication.passwordTip') })
.refine((value) => value === password, {
message: $t('authentication.confirmPasswordTip'),
});
},
triggerFields: ['password'],
},
fieldName: 'confirmPassword',
label: $t('authentication.confirmPassword'),
},
];
});
function handleSubmit(value: Recordable<any>) {
// eslint-disable-next-line no-console
console.log('reset email:', value);
/**
* 处理重置密码操作
* @param values 表单数据
*/
async function handleSubmit(values: Recordable<any>) {
loading.value = true;
try {
const { mobile, code, password } = values;
await smsResetPassword({ mobile, code, password });
message.success($t('authentication.resetPasswordSuccess'));
//
router.push('/');
} catch (error) {
console.error('重置密码失败:', error);
} finally {
loading.value = false;
}
}
</script>
<template>
<AuthenticationForgetPassword
ref="forgetPasswordRef"
:form-schema="formSchema"
:loading="loading"
@submit="handleSubmit"

View File

@ -105,7 +105,7 @@ defineExpose({
@click="handleSubmit"
>
<slot name="submitButtonText">
{{ submitButtonText || $t('authentication.sendResetLink') }}
{{ submitButtonText || $t('authentication.resetPassword') }}
</slot>
</VbenButton>
<VbenButton class="mt-4 w-full" variant="outline" @click="goToLogin()">

View File

@ -33,9 +33,10 @@
"passwordStrength": "Use 8 or more characters with a mix of letters, numbers & symbols",
"forgetPassword": "Forget Password?",
"forgetPasswordSubtitle": "Enter your email and we'll send you instructions to reset your password",
"resetPasswordSuccess": "Reset password success",
"emailTip": "Please enter email",
"emailValidErrorTip": "The email format you entered is incorrect",
"sendResetLink": "Send Reset Link",
"resetPassword": "Reset Password",
"email": "Email",
"qrcodeSubtitle": "Scan the QR code with your phone to login",
"qrcodePrompt": "Click 'Confirm' after scanning to complete login",

View File

@ -33,9 +33,10 @@
"passwordStrength": "使用 8 个或更多字符,混合字母、数字和符号",
"forgetPassword": "忘记密码?",
"forgetPasswordSubtitle": "输入您的电子邮件,我们将向您发送重置密码的连接",
"resetPasswordSuccess": "重置密码成功",
"emailTip": "请输入邮箱",
"emailValidErrorTip": "你输入的邮箱格式不正确",
"sendResetLink": "发送重置链接",
"resetPassword": "重置密码",
"email": "邮箱",
"qrcodeSubtitle": "请用手机扫描二维码登录",
"qrcodePrompt": "扫码后点击 '确认',即可完成登录",