feat:修改信息界面
This commit is contained in:
286
src/views/userInfo/profile/index.vue
Normal file
286
src/views/userInfo/profile/index.vue
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import { useAuthStore } from '@/store/modules/auth';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import type { FormInstance } from 'ant-design-vue';
|
||||||
|
import type { RuleObject } from 'ant-design-vue/es/form';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
interface UserProfile {
|
||||||
|
username: string;
|
||||||
|
fullname: string;
|
||||||
|
birthdate: string;
|
||||||
|
email: string;
|
||||||
|
phonenumber: string;
|
||||||
|
sex: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const formState = ref<UserProfile>({
|
||||||
|
username: '',
|
||||||
|
fullname: '',
|
||||||
|
birthdate: '',
|
||||||
|
email: '',
|
||||||
|
phonenumber: '',
|
||||||
|
sex: '0'
|
||||||
|
});
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const rules = computed<{ [k: string]: RuleObject | RuleObject[] }>(() => {
|
||||||
|
const validateUsername = async (_rule: RuleObject, value: string) => {
|
||||||
|
if (value && (value.length < 4 || value.length > 20)) {
|
||||||
|
return Promise.reject(t('page.login.register.usernameLengthLimit'));
|
||||||
|
}
|
||||||
|
const currentUsername = authStore.userInfo?.user?.userName;
|
||||||
|
if (value && value !== currentUsername) {
|
||||||
|
const { exists } = await authStore.checkUserRepeat({ username: value, authType: 'u' });
|
||||||
|
if (exists) {
|
||||||
|
return Promise.reject(t('page.login.register.usernameExists'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
const validatePhone = async (_rule: RuleObject, value: string) => {
|
||||||
|
if (!value) return Promise.resolve();
|
||||||
|
|
||||||
|
const phonePattern = /^1[3-9]\d{9}$/;
|
||||||
|
if (!phonePattern.test(value)) {
|
||||||
|
return Promise.reject(t('page.login.register.phoneInvalid'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPhone = authStore.userInfo?.user?.phonenumber;
|
||||||
|
if (value !== currentPhone) {
|
||||||
|
const { exists } = await authStore.checkUserRepeat({ phonenumber: value, authType: 'u' });
|
||||||
|
if (exists) {
|
||||||
|
return Promise.reject(t('page.login.register.phoneExists'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateEmail = async (_rule: RuleObject, value: string) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject(t('page.login.register.emailRequired'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
|
||||||
|
if (!emailPattern.test(value)) {
|
||||||
|
return Promise.reject(t('page.login.register.emailInvalid'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentEmail = authStore.userInfo?.user?.email;
|
||||||
|
if (value !== currentEmail) {
|
||||||
|
const { exists } = await authStore.checkUserRepeat({ email: value, authType: 'u' });
|
||||||
|
if (exists) {
|
||||||
|
return Promise.reject(t('page.login.register.emailExists'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
username: [
|
||||||
|
{ required: true, message: t('page.login.register.usernameRequired'), trigger: 'change' },
|
||||||
|
{ validator: validateUsername, trigger: 'blur' }
|
||||||
|
],
|
||||||
|
fullname: [
|
||||||
|
{ required: true, message: t('page.login.register.fullNameRequired'), trigger: 'blur' }
|
||||||
|
],
|
||||||
|
email: [
|
||||||
|
{ required: true, message: t('page.login.register.emailRequired'), trigger: 'change' },
|
||||||
|
{ validator: validateEmail, trigger: 'blur' }
|
||||||
|
],
|
||||||
|
phonenumber: [{ validator: validatePhone, trigger: 'blur' }],
|
||||||
|
birthdate: [
|
||||||
|
{ required: true, message: t('page.login.register.birthDateRequired') },
|
||||||
|
{
|
||||||
|
validator: (_rule: RuleObject, value: string) => {
|
||||||
|
if (value && dayjs(value).isAfter(dayjs())) {
|
||||||
|
return Promise.reject(t('page.login.register.birthDateInvalid'));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const userInfo = authStore.userInfo?.user;
|
||||||
|
if (!userInfo) {
|
||||||
|
message.error(t('page.profile.getUserInfoFailed'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
formState.value = {
|
||||||
|
username: userInfo.userName ?? '',
|
||||||
|
fullname: userInfo.nickName ?? '',
|
||||||
|
birthdate: '',
|
||||||
|
email: userInfo.email ?? '',
|
||||||
|
phonenumber: userInfo.phonenumber ?? '',
|
||||||
|
sex: userInfo.sex ?? '0'
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to get user info:', error);
|
||||||
|
message.error(t('page.profile.updateFailed'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
// 构建更新数据,只包含后端接受的字段
|
||||||
|
// const updateData = {
|
||||||
|
// userName: formState.value.username,
|
||||||
|
// nickName: formState.value.fullname,
|
||||||
|
// email: formState.value.email,
|
||||||
|
// phonenumber: formState.value.phonenumber,
|
||||||
|
// sex: formState.value.sex,
|
||||||
|
// age: formState.value.birthdate ? dayjs().diff(dayjs(formState.value.birthdate), 'year') : undefined
|
||||||
|
// };
|
||||||
|
|
||||||
|
// 这里需要调用后端 API 更新用户信息
|
||||||
|
// await authStore.updateUserProfile(updateData);
|
||||||
|
|
||||||
|
message.success(t('page.profile.updateSuccess'));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('表单验证失败:', error);
|
||||||
|
message.error(t('page.profile.updateFailed'));
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
router.push('/userInfo/usercard');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="profile-container">
|
||||||
|
<a-card :title="t('view.userInfo')" :bordered="false">
|
||||||
|
<template #extra>
|
||||||
|
<a-button @click="handleBack">
|
||||||
|
{{ t('page.login.common.back') }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formState"
|
||||||
|
:rules="rules"
|
||||||
|
:label-col="{ span: 4 }"
|
||||||
|
:wrapper-col="{ span: 16 }"
|
||||||
|
>
|
||||||
|
<a-form-item :label="t('page.login.register.username')" name="username">
|
||||||
|
<a-input
|
||||||
|
v-model:value="formState.username"
|
||||||
|
:placeholder="t('page.login.register.usernameLengthLimit')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item :label="t('page.login.register.fullName')" name="fullname">
|
||||||
|
<a-input
|
||||||
|
v-model:value="formState.fullname"
|
||||||
|
:placeholder="t('page.login.register.fullName')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item :label="t('page.login.register.birthDate')" name="birthdate">
|
||||||
|
<a-date-picker
|
||||||
|
v-model:value="formState.birthdate"
|
||||||
|
style="width: 100%"
|
||||||
|
:format="'YYYY-MM-DD'"
|
||||||
|
:placeholder="t('page.login.register.birthDatePlaceholder')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item :label="t('page.login.register.email')" name="email">
|
||||||
|
<a-input
|
||||||
|
v-model:value="formState.email"
|
||||||
|
:placeholder="t('page.login.register.emailPlaceholder')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item :label="t('page.login.register.phone')" name="phonenumber">
|
||||||
|
<a-input
|
||||||
|
v-model:value="formState.phonenumber"
|
||||||
|
:placeholder="t('page.login.register.phonePlaceholder')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item :label="t('page.login.register.gender')" name="sex">
|
||||||
|
<a-radio-group v-model:value="formState.sex" class="w-full">
|
||||||
|
<a-radio value="0">{{ t('page.login.register.male') }}</a-radio>
|
||||||
|
<a-radio value="1">{{ t('page.login.register.female') }}</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item :wrapper-col="{
|
||||||
|
xs: { span: 24, offset: 0 }, // 移动端下占满宽度,不偏移
|
||||||
|
sm: { span: 16, offset: 4 } // PC端保持原样
|
||||||
|
}" class="text-center">
|
||||||
|
<a-button type="primary" :loading="loading" @click="handleSubmit">
|
||||||
|
{{ t('common.confirm') }}
|
||||||
|
</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.profile-container {
|
||||||
|
padding: 24px;
|
||||||
|
background-color: #f5f5f7;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-card) {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-card-head-title) {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-radio-group) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-form-item:last-child) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-form-item:last-child .ant-form-item-control-input-content) {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端适配 */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
:deep(.ant-form-item-control) {
|
||||||
|
max-width: 100% !important; /* 覆盖 ant-design-vue 的默认样式 */
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-form-item:last-child) {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user