Reflex/src/components/EditProfileForm.vue
2025-05-21 23:43:24 +07:00

273 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="edit-profile-form mt-3">
<h4>Редактировать профиль</h4>
<form @submit.prevent="handleSubmit">
<div class="mb-3">
<label for="editName" class="form-label">Имя:</label>
<input type="text" class="form-control" id="editName" v-model="formData.name" :disabled="profileLoading" />
</div>
<div class="mb-3">
<label for="editBio" class="form-label">О себе:</label>
<textarea class="form-control" id="editBio" rows="3" v-model="formData.bio" :disabled="profileLoading"></textarea>
</div>
<div class="mb-3">
<label for="editDateOfBirth" class="form-label">Дата рождения:</label>
<input type="date" class="form-control" id="editDateOfBirth" v-model="formData.dateOfBirth" :disabled="profileLoading" />
</div>
<div class="mb-3">
<label for="editGender" class="form-label">Пол:</label>
<select class="form-select" id="editGender" v-model="formData.gender" :disabled="profileLoading">
<option value="">Не выбрано</option>
<option value="male">Мужской</option>
<option value="female">Женский</option>
<option value="other">Другой</option>
</select>
</div>
<div v-if="profileSuccessMessage" class="alert alert-success">{{ profileSuccessMessage }}</div>
<div v-if="profileErrorMessage" class="alert alert-danger">{{ profileErrorMessage }}</div>
<button type="submit" class="btn btn-primary" :disabled="profileLoading">
<span v-if="profileLoading" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
{{ profileLoading ? 'Сохранение...' : 'Сохранить изменения' }}
</button>
</form>
<hr class="my-4">
<h4>Загрузить фото профиля</h4>
<form @submit.prevent="handleMultiplePhotoUpload" class="mt-3">
<div class="mb-3">
<label for="profilePhotos" class="form-label">Выберите фото:</label>
<input type="file" class="form-control" id="profilePhotos" @change="onFilesSelected" accept="image/*" multiple :disabled="photoLoading">
</div>
<div v-if="previewUrl" class="mb-3">
<p>Предпросмотр:</p>
<img :src="previewUrl" alt="Предпросмотр фото" class="img-thumbnail" style="max-width: 200px; max-height: 200px;" />
</div>
<div v-if="photoUploadSuccessMessage" class="alert alert-success">{{ photoUploadSuccessMessage }}</div>
<div v-if="photoUploadErrorMessage" class="alert alert-danger">{{ photoUploadErrorMessage }}</div>
<div v-for="(file, index) in selectedFiles" :key="index" class="upload-message" :class="file.status">
{{ file.message }}
</div>
<button type="submit" class="btn btn-secondary" :disabled="photoLoading || selectedFiles.length === 0">
<span v-if="photoLoading" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
{{ photoLoading ? 'Загрузка...' : 'Загрузить фото' }}
</button>
</form>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import { useAuth } from '@/auth';
import api from '@/services/api';
import imageCompression from 'browser-image-compression'; // Импортируем библиотеку
const { user, fetchUser } = useAuth();
const formData = ref({
name: '',
bio: '',
dateOfBirth: '',
gender: '',
});
const profileLoading = ref(false);
const profileErrorMessage = ref('');
const profileSuccessMessage = ref('');
const photoLoading = ref(false);
const photoUploadErrorMessage = ref('');
const photoUploadSuccessMessage = ref('');
const selectedFile = ref(null); // Это будет использоваться для одиночной загрузки, если мы решим ее оставить
const previewUrl = ref(null); // И это
// Новые refs для множественной загрузки
const selectedFiles = ref([]); // Массив выбранных файлов
const photoUploadMessages = ref([]); // Массив сообщений о статусе загрузки каждого файла
const initializeFormData = (currentUser) => {
console.log('[EditProfileForm] Инициализация формы с данными:', currentUser);
if (currentUser) {
formData.value.name = currentUser.name || '';
formData.value.bio = currentUser.bio || '';
formData.value.dateOfBirth = currentUser.dateOfBirth ? new Date(currentUser.dateOfBirth).toISOString().split('T')[0] : '';
formData.value.gender = currentUser.gender || '';
// Не очищаем photoUploadMessages здесь, чтобы пользователь видел результаты предыдущих загрузок в сессии
}
};
onMounted(() => {
console.log('[EditProfileForm] Компонент смонтирован');
initializeFormData(user.value);
});
watch(user, (newUser) => {
console.log('[EditProfileForm] Пользователь изменился:', newUser);
initializeFormData(newUser);
}, { deep: true });
const handleSubmit = async () => {
console.log('[EditProfileForm] Отправка формы профиля...');
profileLoading.value = true;
profileErrorMessage.value = '';
profileSuccessMessage.value = '';
try {
const dataToUpdate = {
name: formData.value.name,
bio: formData.value.bio,
dateOfBirth: formData.value.dateOfBirth || null,
gender: formData.value.gender || null,
};
console.log('[EditProfileForm] Данные для обновления профиля:', dataToUpdate);
const response = await api.updateUserProfile(dataToUpdate);
console.log('[EditProfileForm] Ответ от сервера (профиль):', response.data);
profileSuccessMessage.value = response.data.message || 'Профиль успешно обновлен!';
await fetchUser();
console.log('[EditProfileForm] Данные пользователя обновлены (профиль)');
} catch (err) {
console.error('[EditProfileForm] Ошибка при обновлении профиля:', err);
profileErrorMessage.value = (err.response && err.response.data && err.response.data.message)
? err.response.data.message
: 'Ошибка при обновлении профиля.';
} finally {
profileLoading.value = false;
}
};
// Вместо onFileSelected для одного файла, создадим onFilesSelected для нескольких
const onFilesSelected = (event) => {
const files = event.target.files;
if (files && files.length > 0) {
selectedFiles.value = Array.from(files); // Сохраняем массив файлов
photoUploadMessages.value = selectedFiles.value.map(file => ({ // Инициализируем сообщения для каждого файла
name: file.name,
status: 'pending', // pending, uploading, success, error
message: 'Ожидает загрузки...'
}));
// Очищаем старые одиночные состояния, если они были
selectedFile.value = null;
previewUrl.value = null;
photoUploadErrorMessage.value = '';
photoUploadSuccessMessage.value = '';
} else {
selectedFiles.value = [];
photoUploadMessages.value = [];
}
};
// Обновляем handlePhotoUpload для загрузки нескольких файлов последовательно
const handleMultiplePhotoUpload = async () => {
if (selectedFiles.value.length === 0) {
photoUploadErrorMessage.value = 'Пожалуйста, выберите файлы для загрузки.';
// Очистим индивидуальные сообщения, если они были
photoUploadMessages.value.forEach(msg => {
if (msg.status === 'pending') msg.status = 'cancelled';
});
return;
}
photoLoading.value = true; // Общий индикатор загрузки
photoUploadErrorMessage.value = ''; // Сбрасываем общую ошибку
for (let i = 0; i < selectedFiles.value.length; i++) {
const originalFile = selectedFiles.value[i];
const fileMessageIndex = photoUploadMessages.value.findIndex(m => m.name === originalFile.name && m.status !== 'success');
if (fileMessageIndex === -1) continue;
photoUploadMessages.value[fileMessageIndex].status = 'uploading';
photoUploadMessages.value[fileMessageIndex].message = 'Сжатие и загрузка...';
try {
// Опции сжатия
const options = {
maxSizeMB: 1, // Максимальный размер файла в MB
maxWidthOrHeight: 1920, // Максимальная ширина или высота
useWebWorker: true, // Использовать Web Worker для лучшей производительности
// Дополнительные опции можно найти в документации библиотеки
// Например, initialQuality для JPEG
}
console.log(`[EditProfileForm] Сжатие файла ${originalFile.name}...`);
const compressedFile = await imageCompression(originalFile, options);
console.log(`[EditProfileForm] Файл ${originalFile.name} сжат. Оригинальный размер: ${(originalFile.size / 1024 / 1024).toFixed(2)} MB, Новый размер: ${(compressedFile.size / 1024 / 1024).toFixed(2)} MB`);
const fd = new FormData();
// Важно: сервер ожидает имя файла, поэтому передаем его как третий аргумент
fd.append('profilePhoto', compressedFile, originalFile.name);
console.log(`[EditProfileForm] Отправка файла ${originalFile.name} (сжатого) на сервер...`);
const response = await api.uploadUserProfilePhoto(fd);
console.log(`[EditProfileForm] Ответ от сервера (фото ${originalFile.name}):`, response.data);
photoUploadMessages.value[fileMessageIndex].status = 'success';
photoUploadMessages.value[fileMessageIndex].message = response.data.message || 'Фото успешно загружено!';
await fetchUser();
console.log(`[EditProfileForm] Данные пользователя обновлены (фото ${originalFile.name})`);
} catch (err) {
console.error(`[EditProfileForm] Ошибка при сжатии или загрузке фото ${originalFile.name}:`, err);
photoUploadMessages.value[fileMessageIndex].status = 'error';
// Проверяем, является ли ошибка ошибкой сжатия
if (err instanceof Error && err.message.includes('compression')) {
photoUploadMessages.value[fileMessageIndex].message = `Ошибка при сжатии фото ${originalFile.name}.`;
} else {
photoUploadMessages.value[fileMessageIndex].message = (err.response && err.response.data && err.response.data.message)
? err.response.data.message
: `Ошибка при загрузке фото ${originalFile.name}.`;
}
}
}
// После завершения всех загрузок
selectedFiles.value = []; // Очищаем выбранные файлы
// Не очищаем photoInput.value здесь, т.к. input type="file" multiple сам управляет списком
// Можно сбросить значение инпута, чтобы пользователь мог выбрать те же файлы снова, если захочет
const photoInput = document.getElementById('profilePhotos'); // Убедимся, что ID правильный
if (photoInput) {
photoInput.value = '';
}
photoLoading.value = false; // Выключаем общий индикатор
};
</script>
<style scoped>
.edit-profile-form {
background-color: #f8f9fa;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.img-thumbnail {
border: 1px solid #dee2e6;
padding: 0.25rem;
background-color: #fff;
border-radius: 0.25rem;
}
.upload-message {
font-size: 0.9em;
}
.upload-message.success {
color: green;
}
.upload-message.error {
color: red;
}
.upload-message.pending, .upload-message.uploading {
color: orange;
}
</style>