Browse Source

Merge pull request #1159 from colinin/profile-bind-change

Profile bind change
pull/1162/head
yx lin 10 months ago
committed by GitHub
parent
commit
9d310cb71e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 24
      apps/vben5/apps/app-antd/src/layouts/basic.vue
  2. 28
      apps/vben5/packages/@abp/account/src/api/useProfileApi.ts
  3. 23
      apps/vben5/packages/@abp/account/src/components/MySetting.vue
  4. 110
      apps/vben5/packages/@abp/account/src/components/components/ChangePasswordModal.vue
  5. 82
      apps/vben5/packages/@abp/account/src/components/components/ChangePhoneNumberModal.vue
  6. 11
      apps/vben5/packages/@abp/account/src/types/profile.ts
  7. 2
      apps/vben5/packages/@abp/identity/src/hooks/usePasswordValidator.ts
  8. 1
      apps/vben5/packages/@abp/identity/src/index.ts
  9. 4
      apps/vben5/packages/@abp/settings/src/components/settings/SettingForm.vue

24
apps/vben5/apps/app-antd/src/layouts/basic.vue

@ -23,6 +23,8 @@ import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';
import { useAbpStore } from '@abp/core';
import { useSessions } from '#/hooks/useSessions';
import { $t } from '#/locales';
import { useAuthStore } from '#/store';
@ -64,6 +66,7 @@ const notifications = ref<NotificationItem[]>([
useSessions();
const { replace } = useRouter();
const abpStore = useAbpStore();
const userStore = useUserStore();
const authStore = useAuthStore();
const accessStore = useAccessStore();
@ -109,8 +112,19 @@ const menus = computed(() => [
},
]);
const userInfo = computed(() => {
return userStore.userInfo;
});
const description = computed(() => {
if (abpStore.application?.currentTenant.name && userInfo.value?.username) {
return `${abpStore.application.currentTenant.name}/${userInfo.value.username}`;
}
return userInfo.value?.username;
});
const avatar = computed(() => {
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
return userInfo.value?.avatar ?? preferences.app.defaultAvatar;
});
async function handleLogout() {
@ -129,7 +143,7 @@ watch(
async (enable) => {
if (enable) {
await updateWatermark({
content: `${userStore.userInfo?.username}`,
content: `${userInfo.value?.username}`,
});
} else {
destroyWatermark();
@ -146,10 +160,10 @@ watch(
<template #user-dropdown>
<UserDropdown
:avatar
:description="userStore.userInfo?.email"
:description="description"
:menus
:tag-text="userStore.userInfo?.username"
:text="userStore.userInfo?.realName"
:tag-text="userInfo?.email"
:text="userInfo?.realName"
@logout="handleLogout"
/>
</template>

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

@ -2,9 +2,11 @@ import type {
AuthenticatorDto,
AuthenticatorRecoveryCodeDto,
ChangePasswordInput,
ChangePhoneNumberInput,
ChangePictureInput,
ConfirmEmailInput,
ProfileDto,
SendChangePhoneNumberCodeInput,
SendEmailConfirmCodeDto,
TwoFactorEnabledDto,
UpdateProfileDto,
@ -49,6 +51,30 @@ export function useProfileApi() {
});
}
/**
*
* @param input
*/
function sendChangePhoneNumberCodeApi(
input: SendChangePhoneNumberCodeInput,
): Promise<void> {
return request('/api/account/my-profile/send-phone-number-change-code', {
data: input,
method: 'POST',
});
}
/**
*
* @param input
*/
function changePhoneNumberApi(input: ChangePhoneNumberInput): Promise<void> {
return request('/api/account/my-profile/change-phone-number', {
data: input,
method: 'PUT',
});
}
/**
*
* @param input
@ -158,6 +184,7 @@ export function useProfileApi() {
return {
cancel,
changePasswordApi,
changePhoneNumberApi,
changePictureApi,
changeTwoFactorEnabledApi,
confirmEmailApi,
@ -166,6 +193,7 @@ export function useProfileApi() {
getPictureApi,
getTwoFactorEnabledApi,
resetAuthenticatorApi,
sendChangePhoneNumberCodeApi,
sendEmailConfirmLinkApi,
updateApi,
verifyAuthenticatorCodeApi,

23
apps/vben5/packages/@abp/account/src/components/MySetting.vue

@ -91,6 +91,16 @@ const [EmailConfirmModal, emailConfirmModalApi] = useVbenModal({
() => import('./components/EmailConfirmModal.vue'),
),
});
const [ChangePasswordModal, changePasswordModalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./components/ChangePasswordModal.vue'),
),
});
const [ChangePhoneNumberModal, changePhoneNumberModalApi] = useVbenModal({
connectedComponent: defineAsyncComponent(
() => import('./components/ChangePhoneNumberModal.vue'),
),
});
function onEmailConfirm() {
if (query?.confirmToken) {
emailConfirmModalApi.setData({
@ -104,6 +114,11 @@ async function onGetProfile() {
const profile = await getApi();
myProfile.value = profile;
}
async function onPhoneNumberChange(phoneNumber: string) {
userStore.$patch((state) => {
state.userInfo && (state.userInfo.phoneNumber = phoneNumber);
});
}
async function onUpdateProfile(input: UpdateProfileDto) {
Modal.confirm({
centered: true,
@ -120,12 +135,10 @@ async function onUpdateProfile(input: UpdateProfileDto) {
});
}
function onChangePassword() {
// TODO: onChangePassword !
console.warn('onChangePassword 暂时未实现!');
changePasswordModalApi.open();
}
function onChangePhoneNumber() {
// TODO: onChangePhoneNumber !
console.warn('onChangePhoneNumber 暂时未实现!');
changePhoneNumberModalApi.open();
}
onMounted(async () => {
await onGetProfile();
@ -169,6 +182,8 @@ onMounted(async () => {
</div>
</Card>
<EmailConfirmModal />
<ChangePasswordModal />
<ChangePhoneNumberModal @change="onPhoneNumberChange" />
</div>
</template>

110
apps/vben5/packages/@abp/account/src/components/components/ChangePasswordModal.vue

@ -0,0 +1,110 @@
<script setup lang="ts">
import { useVbenForm, useVbenModal, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { usePasswordValidator } from '@abp/identity';
import { message } from 'ant-design-vue';
import { useProfileApi } from '../../api';
const { validate } = usePasswordValidator();
const { changePasswordApi } = useProfileApi();
interface FormModel {
currentPassword: string;
newPassword: string;
newPasswordConfirm: string;
}
const [Form, formApi] = useVbenForm({
handleSubmit: onSubmit,
schema: [
{
component: 'InputPassword',
fieldName: 'currentPassword',
label: $t('AbpAccount.DisplayName:CurrentPassword'),
rules: 'required',
},
{
component: 'InputPassword',
fieldName: 'newPassword',
label: $t('AbpAccount.DisplayName:NewPassword'),
rules: z
.string()
.superRefine(async (newPassword, ctx) => {
try {
await validate(newPassword);
} catch (error) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: String(error),
});
}
})
.refine(
async (newPassword) => {
const input = (await formApi.getValues()) as FormModel;
return input.currentPassword !== newPassword;
},
{
message: $t('AbpAccount.NewPasswordSameAsOld'),
},
)
.refine(
async (newPassword) => {
const input = (await formApi.getValues()) as FormModel;
return input.newPasswordConfirm === newPassword;
},
{
message: $t(
'AbpIdentity.Volo_Abp_Identity:PasswordConfirmationFailed',
),
},
),
},
{
component: 'InputPassword',
fieldName: 'newPasswordConfirm',
label: $t('AbpAccount.DisplayName:NewPasswordConfirm'),
rules: z.string().refine(
async (newPasswordConfirm) => {
const input = (await formApi.getValues()) as FormModel;
return input.newPassword === newPasswordConfirm;
},
{
message: $t(
'AbpIdentity.Volo_Abp_Identity:PasswordConfirmationFailed',
),
},
),
},
],
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
await formApi.validateAndSubmitForm();
},
});
async function onSubmit(values: Record<string, any>) {
try {
modalApi.setState({ submitting: true });
await changePasswordApi({
currentPassword: values.currentPassword,
newPassword: values.newPassword,
});
message.success($t('AbpIdentity.PasswordChangedMessage'));
modalApi.close();
} finally {
modalApi.setState({ submitting: false });
}
}
</script>
<template>
<Modal :title="$t('AbpAccount.ResetMyPassword')">
<Form />
</Modal>
</template>
<style scoped></style>

82
apps/vben5/packages/@abp/account/src/components/components/ChangePhoneNumberModal.vue

@ -0,0 +1,82 @@
<script setup lang="ts">
import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { message } from 'ant-design-vue';
import { useProfileApi } from '../../api';
const emits = defineEmits<{
(event: 'change', data: string): void;
}>();
const { changePhoneNumberApi, sendChangePhoneNumberCodeApi } = useProfileApi();
const [Form, formApi] = useVbenForm({
handleSubmit: onSubmit,
schema: [
{
component: 'Input',
fieldName: 'newPhoneNumber',
label: $t('AbpIdentity.DisplayName:NewPhoneNumber'),
rules: 'required',
},
{
component: 'VbenPinInput',
componentProps: {
createText: (countdown: number) => {
const text =
countdown > 0
? $t('authentication.sendText', [countdown])
: $t('authentication.sendCode');
return text;
},
handleSendCode: onSendCode,
},
fieldName: 'code',
label: $t('AbpIdentity.DisplayName:SmsVerifyCode'),
rules: 'required',
},
],
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
await formApi.validateAndSubmitForm();
},
});
async function onSendCode() {
const result = await formApi.validateField('newPhoneNumber');
if (!result.valid) {
throw new Error(result.errors.join('\n'));
}
const input = await formApi.getValues();
await sendChangePhoneNumberCodeApi({
newPhoneNumber: input.newPhoneNumber,
});
}
async function onSubmit(values: Record<string, any>) {
try {
modalApi.setState({ submitting: true });
await changePhoneNumberApi({
code: values.code,
newPhoneNumber: values.newPhoneNumber,
});
message.success($t('AbpAccount.PhoneNumberChangedMessage'));
emits('change', values.newPhoneNumber);
modalApi.close();
} finally {
modalApi.setState({ submitting: false });
}
}
</script>
<template>
<Modal :title="$t('AbpIdentity.PhoneNumber')">
<Form />
</Modal>
</template>
<style scoped></style>

11
apps/vben5/packages/@abp/account/src/types/profile.ts

@ -36,6 +36,15 @@ interface ChangePasswordInput {
newPassword: string;
}
interface ChangePhoneNumberInput {
code: string;
newPhoneNumber: string;
}
interface SendChangePhoneNumberCodeInput {
newPhoneNumber: string;
}
interface ChangePictureInput {
file: File;
}
@ -73,9 +82,11 @@ export type {
AuthenticatorDto,
AuthenticatorRecoveryCodeDto,
ChangePasswordInput,
ChangePhoneNumberInput,
ChangePictureInput,
ConfirmEmailInput,
ProfileDto,
SendChangePhoneNumberCodeInput,
SendEmailConfirmCodeDto,
TwoFactorEnabledDto,
UpdateProfileDto,

2
apps/vben5/packages/@abp/identity/src/hooks/usePasswordValidator.ts

@ -14,7 +14,7 @@ import {
export function usePasswordValidator() {
const { getNumber, isTrue } = useSettings();
const { L } = useLocalization(['AbpIdentity', 'AbpUi']);
const { L } = useLocalization(['AbpIdentity', 'AbpValidation', 'AbpUi']);
const passwordSetting = computed(() => {
return {

1
apps/vben5/packages/@abp/identity/src/index.ts

@ -1,4 +1,5 @@
export * from './api';
export * from './components';
export { UserLookupPermissions } from './constants/permissions';
export * from './hooks';
export * from './types';

4
apps/vben5/packages/@abp/settings/src/components/settings/SettingForm.vue

@ -78,7 +78,7 @@ async function onSubmit() {
}
function onCheckChange(setting: SettingDetail) {
setting.value = setting.value === 'true' ? 'false' : 'true';
setting.value = setting.value?.toLowerCase() === 'true' ? 'false' : 'true';
onValueChange(setting);
}
@ -202,7 +202,7 @@ onMounted(onGet);
</Select>
<Checkbox
v-if="detail.valueType === ValueType.Boolean"
:checked="detail.value === 'true'"
:checked="detail.value?.toLowerCase() === 'true'"
@change="onCheckChange(detail)"
>
{{ detail.displayName }}

Loading…
Cancel
Save