|
|
@@ -0,0 +1,312 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import { onMounted, reactive, ref } from 'vue';
|
|
|
+
|
|
|
+import { useAccessStore, useUserStore } from '@vben/stores';
|
|
|
+
|
|
|
+import { $t } from '@/locales';
|
|
|
+import {
|
|
|
+ Button,
|
|
|
+ DatePicker,
|
|
|
+ Input,
|
|
|
+ message,
|
|
|
+ Radio,
|
|
|
+ RadioGroup,
|
|
|
+ Upload,
|
|
|
+} from 'antdv-next';
|
|
|
+import dayjs from 'dayjs';
|
|
|
+import localeData from 'dayjs/plugin/localeData';
|
|
|
+import weekday from 'dayjs/plugin/weekday';
|
|
|
+
|
|
|
+import { getUserInfoApi, updateUserInfoApi } from '#/api';
|
|
|
+
|
|
|
+dayjs.extend(weekday);
|
|
|
+dayjs.extend(localeData);
|
|
|
+
|
|
|
+const userStore = useUserStore();
|
|
|
+const accessStore = useAccessStore();
|
|
|
+const loading = ref(false);
|
|
|
+
|
|
|
+const formData = reactive({
|
|
|
+ id: '',
|
|
|
+ gogs_email: '',
|
|
|
+ gender: '-1',
|
|
|
+ birthday: null as any,
|
|
|
+ avatarFileId: '',
|
|
|
+ fileList: [] as any[],
|
|
|
+ langNameList: [
|
|
|
+ {
|
|
|
+ name: 'zh-CN',
|
|
|
+ value: '',
|
|
|
+ },
|
|
|
+ ] as { name: string; value: string }[],
|
|
|
+});
|
|
|
+
|
|
|
+const genderOptions = [
|
|
|
+ { label: $t('userProfile.genderOptions.secret'), value: '-1' },
|
|
|
+ { label: $t('userProfile.genderOptions.male'), value: '1' },
|
|
|
+ { label: $t('userProfile.genderOptions.female'), value: '0' },
|
|
|
+];
|
|
|
+
|
|
|
+function handleAvatarUpload(info: any) {
|
|
|
+ if (info.file.status === 'done') {
|
|
|
+ if (info.file.response?.result?.[0]?.id) {
|
|
|
+ formData.avatarFileId = info.file.response.result[0].id;
|
|
|
+ message.success($t('userProfile.messages.avatarUploadSuccess'));
|
|
|
+ } else {
|
|
|
+ message.error($t('userProfile.messages.avatarUploadFailed'));
|
|
|
+ }
|
|
|
+ } else if (info.file.status === 'error') {
|
|
|
+ message.error($t('userProfile.messages.avatarUploadFailed'));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function handleSubmit() {
|
|
|
+ if (!formData.id) {
|
|
|
+ message.error($t('userProfile.messages.userIdRequired'));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ const submitData = {
|
|
|
+ ...formData,
|
|
|
+ birthday: formData.birthday ? formData.birthday.format('YYYY-MM-DD') : '',
|
|
|
+ };
|
|
|
+
|
|
|
+ const result = await updateUserInfoApi(submitData);
|
|
|
+ if (result && result.isSuccess) {
|
|
|
+ message.success($t('userProfile.messages.updateSuccess'));
|
|
|
+
|
|
|
+ const userInfo = await getUserInfoApi();
|
|
|
+ if (userInfo && userInfo.isSuccess) {
|
|
|
+ const updatedUserInfo = {
|
|
|
+ account:
|
|
|
+ userInfo.result?.account || userInfo.result?.englishName || '',
|
|
|
+ avatar: userInfo.result?.avatarFileId || '',
|
|
|
+ cellPhone: userInfo.result?.cellPhone || '',
|
|
|
+ realName: userInfo.result?.chineseName || userInfo.result?.name || '',
|
|
|
+ email: userInfo.result?.emailAddress || '',
|
|
|
+ roles: [],
|
|
|
+ userId: userInfo.result?.id || '',
|
|
|
+ username: userInfo.result?.name || '',
|
|
|
+ };
|
|
|
+ userStore.setUserInfo(updatedUserInfo);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ message.error($t('userProfile.messages.updateFailed'));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('更新失败:', error);
|
|
|
+ message.error($t('userProfile.messages.updateFailed'));
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function handleBack() {
|
|
|
+ window.history.back();
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ const localUserInfo = userStore.userInfo;
|
|
|
+ if (localUserInfo) {
|
|
|
+ formData.id = localUserInfo.userId || '';
|
|
|
+ formData.gogs_email = localUserInfo.email || '';
|
|
|
+ formData.avatarFileId = localUserInfo.avatar || '';
|
|
|
+ if (formData.langNameList && formData.langNameList[0]) {
|
|
|
+ formData.langNameList[0].value = localUserInfo.realName || '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const userInfo = await getUserInfoApi();
|
|
|
+ if (userInfo && userInfo.isSuccess && userInfo.result) {
|
|
|
+ const info = userInfo.result;
|
|
|
+ formData.id = info.id || '';
|
|
|
+ formData.gogs_email = (info as any).gogs_email || '';
|
|
|
+ formData.gender = (info as any).gender?.toString() || '-1';
|
|
|
+ formData.birthday = (info as any).birthday
|
|
|
+ ? dayjs((info as any).birthday)
|
|
|
+ : null;
|
|
|
+ formData.avatarFileId = info.avatarFileId || '';
|
|
|
+ if (formData.langNameList && formData.langNameList.length > 0) {
|
|
|
+ formData.langNameList[0]!.value = info.chineseName || info.name || '';
|
|
|
+ }
|
|
|
+
|
|
|
+ if (info.avatarFileId) {
|
|
|
+ formData.fileList = [
|
|
|
+ {
|
|
|
+ uid: '-1',
|
|
|
+ name: 'avatar.png',
|
|
|
+ status: 'done',
|
|
|
+ url: `/File/Download?fileId=${info.avatarFileId}`,
|
|
|
+ response: {
|
|
|
+ result: [
|
|
|
+ {
|
|
|
+ id: info.avatarFileId,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div class="p-6">
|
|
|
+ <div class="mb-4 flex items-center justify-between">
|
|
|
+ <div class="text-sm text-[#462424] text-gray-500">
|
|
|
+ {{ $t('userProfile.breadcrumb') }}
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="flex cursor-pointer items-center gap-[9px] text-[16px] font-bold text-[#462424]"
|
|
|
+ @click="handleBack"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="global-color flex h-[22px] w-[22px] items-center justify-center rounded-full"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ alt=""
|
|
|
+ class="h-[11px] w-[10px]"
|
|
|
+ src="@/assets/image/the-left.png"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ {{ $t('btn.back') }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="mb-8 text-[26px] font-bold text-[#462424]">
|
|
|
+ {{ $t('userProfile.title') }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="mx-auto max-w-3xl rounded-lg bg-white p-8 shadow-md">
|
|
|
+ <div class="mb-10 flex flex-col items-center">
|
|
|
+ <Upload
|
|
|
+ v-model:file-list="formData.fileList"
|
|
|
+ :action="`/fileApi/File/UploadFiles?Authorization=${accessStore.accessToken}`"
|
|
|
+ :headers="{ Authorization: String(accessStore.accessToken) }"
|
|
|
+ :max-count="1"
|
|
|
+ :show-upload-list="false"
|
|
|
+ class="mb-4"
|
|
|
+ list-type="picture"
|
|
|
+ @change="handleAvatarUpload"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="flex h-[120px] w-[120px] cursor-pointer items-center justify-center overflow-hidden rounded-full border-2 border-dashed border-gray-300"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ v-if="formData.avatarFileId"
|
|
|
+ :alt="$t('userProfile.avatar')"
|
|
|
+ :src="`/File/Download?fileId=${formData.avatarFileId}`"
|
|
|
+ class="h-full w-full rounded-full object-cover"
|
|
|
+ />
|
|
|
+ <div
|
|
|
+ v-else
|
|
|
+ class="flex flex-col items-center text-sm text-gray-400"
|
|
|
+ >
|
|
|
+ {{ $t('userProfile.uploadAvatar') }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Upload>
|
|
|
+ <div class="text-sm text-gray-500">
|
|
|
+ {{ $t('userProfile.clickToChangeAvatar') }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="space-y-6">
|
|
|
+ <div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
|
+ <div class="flex flex-col gap-2">
|
|
|
+ <label class="text-sm font-medium text-gray-700">{{
|
|
|
+ $t('userProfile.username')
|
|
|
+ }}</label>
|
|
|
+ <Input
|
|
|
+ v-model:value="formData.langNameList[0]!.value"
|
|
|
+ class="h-[48px] rounded-[12px] border-[#e0e0e0]"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex flex-col gap-2">
|
|
|
+ <label class="text-sm font-medium text-gray-700">{{
|
|
|
+ $t('userProfile.gitAccount')
|
|
|
+ }}</label>
|
|
|
+ <Input
|
|
|
+ v-model:value="formData.gogs_email"
|
|
|
+ class="h-[48px] rounded-[12px] border-[#e0e0e0]"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
|
+ <div class="flex flex-col gap-2">
|
|
|
+ <label class="text-sm font-medium text-gray-700">{{
|
|
|
+ $t('userProfile.gender')
|
|
|
+ }}</label>
|
|
|
+ <RadioGroup v-model:value="formData.gender" class="mt-2">
|
|
|
+ <Radio
|
|
|
+ v-for="option in genderOptions"
|
|
|
+ :key="option.value"
|
|
|
+ :value="option.value"
|
|
|
+ class="mr-4"
|
|
|
+ >
|
|
|
+ {{ option.label }}
|
|
|
+ </Radio>
|
|
|
+ </RadioGroup>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="flex flex-col gap-2">
|
|
|
+ <label class="text-sm font-medium text-gray-700">{{
|
|
|
+ $t('userProfile.birthday')
|
|
|
+ }}</label>
|
|
|
+ <DatePicker
|
|
|
+ v-model:value="formData.birthday"
|
|
|
+ class="h-[48px] rounded-[12px] border-[#e0e0e0]"
|
|
|
+ format="YYYY-MM-DD"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="mt-12 flex justify-center gap-4">
|
|
|
+ <Button
|
|
|
+ class="h-[42px] rounded-[12px] border-[#e0e0e0] px-8"
|
|
|
+ @click="handleBack"
|
|
|
+ >
|
|
|
+ {{ $t('userProfile.cancel') }}
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ :loading="loading"
|
|
|
+ class="h-[42px] rounded-[12px] bg-[#462424] px-8 text-white"
|
|
|
+ type="primary"
|
|
|
+ @click="handleSubmit"
|
|
|
+ >
|
|
|
+ {{ $t('userProfile.save') }}
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+:deep(.ant-radio-group) {
|
|
|
+ display: flex;
|
|
|
+ gap: 24px;
|
|
|
+ margin-top: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.ant-radio-button-wrapper) {
|
|
|
+ margin-right: 12px !important;
|
|
|
+ border-radius: 8px !important;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.ant-radio-button-wrapper-checked) {
|
|
|
+ color: white !important;
|
|
|
+ background-color: #462424 !important;
|
|
|
+ border-color: #462424 !important;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.ant-radio-button-wrapper-checked:hover) {
|
|
|
+ background-color: #5a3434 !important;
|
|
|
+ border-color: #5a3434 !important;
|
|
|
+}
|
|
|
+</style>
|