Reflex/src/components/EditProfileForm.vue

273 lines
13 KiB
Vue
Raw Normal View History

2025-05-21 22:13:09 +07:00
<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>
2025-05-21 23:43:24 +07:00
<form @submit.prevent="handleMultiplePhotoUpload" class="mt-3">
2025-05-21 22:13:09 +07:00
<div class="mb-3">
2025-05-21 23:43:24 +07:00
<label for="profilePhotos" class="form-label">Выберите фото:</label>
<input type="file" class="form-control" id="profilePhotos" @change="onFilesSelected" accept="image/*" multiple :disabled="photoLoading">
2025-05-21 22:13:09 +07:00
</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>
2025-05-21 23:43:24 +07:00
<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">
2025-05-21 22:13:09 +07:00
<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';
2025-05-21 23:43:24 +07:00
import imageCompression from 'browser-image-compression'; // Импортируем библиотеку
2025-05-21 22:13:09 +07:00
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('');
2025-05-21 23:43:24 +07:00
const selectedFile = ref(null); // Это будет использоваться для одиночной загрузки, если мы решим ее оставить
const previewUrl = ref(null); // И это
2025-05-21 22:13:09 +07:00
2025-05-21 23:43:24 +07:00
// Новые refs для множественной загрузки
const selectedFiles = ref([]); // Массив выбранных файлов
const photoUploadMessages = ref([]); // Массив сообщений о статусе загрузки каждого файла
2025-05-21 22:13:09 +07:00
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 || '';
2025-05-21 23:43:24 +07:00
// Не очищаем photoUploadMessages здесь, чтобы пользователь видел результаты предыдущих загрузок в сессии
2025-05-21 22:13:09 +07:00
}
};
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;
}
};
2025-05-21 23:43:24 +07:00
// Вместо 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: 'Ожидает загрузки...'
}));
// Очищаем старые одиночные состояния, если они были
2025-05-21 22:13:09 +07:00
selectedFile.value = null;
previewUrl.value = null;
2025-05-21 23:43:24 +07:00
photoUploadErrorMessage.value = '';
photoUploadSuccessMessage.value = '';
} else {
selectedFiles.value = [];
photoUploadMessages.value = [];
2025-05-21 22:13:09 +07:00
}
};
2025-05-21 23:43:24 +07:00
// Обновляем handlePhotoUpload для загрузки нескольких файлов последовательно
const handleMultiplePhotoUpload = async () => {
if (selectedFiles.value.length === 0) {
photoUploadErrorMessage.value = 'Пожалуйста, выберите файлы для загрузки.';
// Очистим индивидуальные сообщения, если они были
photoUploadMessages.value.forEach(msg => {
if (msg.status === 'pending') msg.status = 'cancelled';
});
2025-05-21 22:13:09 +07:00
return;
}
2025-05-21 23:43:24 +07:00
photoLoading.value = true; // Общий индикатор загрузки
photoUploadErrorMessage.value = ''; // Сбрасываем общую ошибку
2025-05-21 22:13:09 +07:00
2025-05-21 23:43:24 +07:00
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;
2025-05-21 22:13:09 +07:00
2025-05-21 23:43:24 +07:00
photoUploadMessages.value[fileMessageIndex].status = 'uploading';
photoUploadMessages.value[fileMessageIndex].message = 'Сжатие и загрузка...';
2025-05-21 22:13:09 +07:00
2025-05-21 23:43:24 +07:00
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 = '';
2025-05-21 22:13:09 +07:00
}
2025-05-21 23:43:24 +07:00
photoLoading.value = false; // Выключаем общий индикатор
2025-05-21 22:13:09 +07:00
};
</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;
}
2025-05-21 23:43:24 +07:00
.upload-message {
font-size: 0.9em;
}
.upload-message.success {
color: green;
}
.upload-message.error {
color: red;
}
.upload-message.pending, .upload-message.uploading {
color: orange;
}
2025-05-21 22:13:09 +07:00
</style>