From 118597a73c4f169c67709fb71383e46c3490daac Mon Sep 17 00:00:00 2001 From: 107 <107@DESKTOP-UP8U7M2> Date: Fri, 23 May 2025 13:16:13 +0700 Subject: [PATCH] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D1=84=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/EditProfileForm.vue | 31 +++++ src/views/ProfileView.vue | 194 ++++++++++++++++++++++++++--- 2 files changed, 207 insertions(+), 18 deletions(-) diff --git a/src/components/EditProfileForm.vue b/src/components/EditProfileForm.vue index 1209e63..7b92209 100644 --- a/src/components/EditProfileForm.vue +++ b/src/components/EditProfileForm.vue @@ -411,6 +411,37 @@ const handlePhotoUpload = async (files) => { console.log(`[EditProfileForm] handlePhotoUpload: получено файлов: ${files.length}`); + // Проверяем форматы и размеры файлов + const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/jpg']; + const maxSizeMB = 10; // Максимальный размер файла в MB + const maxSizeBytes = maxSizeMB * 1024 * 1024; + + let hasInvalidType = false; + let hasInvalidSize = false; + + for (const file of files) { + if (!allowedTypes.includes(file.type)) { + hasInvalidType = true; + break; + } + if (file.size > maxSizeBytes) { + hasInvalidSize = true; + break; + } + } + + if (hasInvalidType) { + photoUploadErrorMessage.value = 'Можно загружать только фотографии (JPEG, PNG, WebP)'; + emit('photo-upload-complete', { success: 0, errors: files.length }); + return; + } + + if (hasInvalidSize) { + photoUploadErrorMessage.value = `Размер файла не должен превышать ${maxSizeMB} МБ`; + emit('photo-upload-complete', { success: 0, errors: files.length }); + return; + } + // Индикатор для отслеживания успешных загрузок let successCount = 0; let errorCount = 0; diff --git a/src/views/ProfileView.vue b/src/views/ProfileView.vue index 8666fae..9f71b84 100644 --- a/src/views/ProfileView.vue +++ b/src/views/ProfileView.vue @@ -122,16 +122,14 @@ - - - +
-

Мои фотографии

-
-
@@ -170,18 +168,17 @@
- -
+

Нет фотографий

Добавьте фотографии, чтобы сделать профиль привлекательнее

-
- - +
{{ photoActionError }} @@ -190,6 +187,10 @@ {{ photoActionSuccess }}
+
+
+ Загрузка фотографий... +
@@ -306,6 +307,7 @@ const scrollToEdit = () => { const triggerPhotoUpload = () => { if (fileInput.value) { + clearMessages(); // Очищаем предыдущие сообщения перед новой загрузкой fileInput.value.click(); } }; @@ -313,10 +315,43 @@ const triggerPhotoUpload = () => { const handlePhotoSelect = async (event) => { const files = event.target.files; if (!files || files.length === 0) return; - - // Передаем выбранные файлы в форму редактирования - if (editFormRef.value && editFormRef.value.handlePhotoUpload) { + // Проверяем форматы и размеры файлов + const allowedTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/jpg']; + const maxSizeMB = 10; // Максимальный размер файла в MB + const maxSizeBytes = maxSizeMB * 1024 * 1024; + + let hasInvalidType = false; + let hasInvalidSize = false; + + for (const file of files) { + if (!allowedTypes.includes(file.type)) { + hasInvalidType = true; + break; + } + if (file.size > maxSizeBytes) { + hasInvalidSize = true; + break; + } + } + + if (hasInvalidType) { + photoActionError.value = 'Можно загружать только фотографии (JPEG, PNG, WebP)'; + event.target.value = ''; + return; + } + + if (hasInvalidSize) { + photoActionError.value = `Размер файла не должен превышать ${maxSizeMB} МБ`; + event.target.value = ''; + return; + } + + // Если активна форма редактирования, используем её метод + if (isEditMode.value && editFormRef.value && editFormRef.value.handlePhotoUpload) { await editFormRef.value.handlePhotoUpload(Array.from(files)); + } else { + // Иначе обрабатываем загрузку фото здесь + await uploadPhotos(Array.from(files)); } // Очищаем input @@ -387,6 +422,102 @@ const handlePhotoUploadComplete = (result) => { } }; +// Новый метод для загрузки фотографий непосредственно из ProfileView +const uploadPhotos = async (files) => { + if (!files || files.length === 0) { + console.log('[ProfileView] uploadPhotos: нет файлов для загрузки'); + return; + } + + console.log(`[ProfileView] uploadPhotos: получено файлов: ${files.length}`); + + // Индикатор для отслеживания успешных загрузок + let successCount = 0; + let errorCount = 0; + photoActionLoading.value = true; + clearMessages(); + try { + let imageCompression; + try { + // Импортируем библиотеку для сжатия изображений динамически + imageCompression = (await import('browser-image-compression')).default; + } catch (importErr) { + console.error('[ProfileView] Ошибка при импорте библиотеки сжатия:', importErr); + throw new Error('Не удалось загрузить необходимые компоненты. Пожалуйста, обновите страницу и попробуйте снова.'); + } + + // Настройки для сжатия изображений + const options = { + maxSizeMB: 1, + maxWidthOrHeight: 1920, + useWebWorker: true, + }; + + for (let i = 0; i < files.length; i++) { + const originalFile = files[i]; + + try { + // Сжимаем файл + console.log(`[ProfileView] Сжатие файла ${originalFile.name}...`); + const compressedFile = await imageCompression(originalFile, options); + console.log(`[ProfileView] Файл ${originalFile.name} сжат. Оригинальный размер: ${(originalFile.size / 1024 / 1024).toFixed(2)} MB, Новый размер: ${(compressedFile.size / 1024 / 1024).toFixed(2)} MB`); + + // Создаем FormData для отправки на сервер + const fd = new FormData(); + fd.append('profilePhoto', compressedFile, originalFile.name); + + // Отправляем файл + console.log(`[ProfileView] Отправка файла ${originalFile.name} (сжатого) на сервер...`); + const response = await api.uploadUserProfilePhoto(fd); + console.log(`[ProfileView] Ответ сервера (фото ${originalFile.name}):`, response.data); + + // Увеличиваем счетчик успешных загрузок + successCount++; + + // Обновляем данные пользователя после каждой успешной загрузки + await fetchUser(); + console.log(`[ProfileView] Данные пользователя обновлены после загрузки фото ${originalFile.name}`); + } catch (err) { + console.error(`[ProfileView] Ошибка при сжатии или загрузке фото ${originalFile.name}:`, err); + errorCount++; + + // Показываем более информативное сообщение об ошибке + if (err.message && err.message.includes('network')) { + photoActionError.value = 'Ошибка сети при загрузке фото. Пожалуйста, проверьте подключение к интернету.'; + } else if (err.response && err.response.status === 413) { + photoActionError.value = 'Файл слишком большой. Пожалуйста, выберите фото меньшего размера.'; + } + } + } + + // Формируем сообщение для пользователя + if (successCount > 0) { + photoActionSuccess.value = `Успешно загружено фотографий: ${successCount}`; + console.log(`[ProfileView] Успешно загружено фотографий: ${successCount}`); + + // Автоматически скрываем сообщение через 3 секунды + setTimeout(() => { + photoActionSuccess.value = ''; + }, 3000); + + // Прокручиваем к разделу с фотографиями + setTimeout(() => { + scrollToPhotos(); + }, 100); + } + + if (errorCount > 0) { + photoActionError.value = `Не удалось загрузить некоторые фото (${errorCount})`; + console.log(`[ProfileView] Ошибок при загрузке: ${errorCount}`); + } + } catch (err) { + console.error('[ProfileView] Общая ошибка при загрузке фото:', err); + photoActionError.value = 'Произошла ошибка при загрузке фотографий'; + } finally { + photoActionLoading.value = false; + } +}; + const clearMessages = () => { photoActionError.value = ''; photoActionSuccess.value = ''; @@ -959,6 +1090,9 @@ onMounted(async () => { font-weight: 600; cursor: pointer; transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 0.5rem; } .add-photo-btn:hover { @@ -966,6 +1100,11 @@ onMounted(async () => { box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); } +.add-photo-btn:active { + transform: translateY(0); + box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); +} + .photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); @@ -1088,6 +1227,12 @@ onMounted(async () => { border: 1px solid rgba(40, 167, 69, 0.2); } +.alert.info { + background: rgba(102, 126, 234, 0.1); + color: #3c4d8d; + border: 1px solid rgba(102, 126, 234, 0.2); +} + /* Modal */ .modal-overlay { position: fixed; @@ -1199,4 +1344,17 @@ onMounted(async () => { padding: 1.5rem; } } + +/* Spinner для кнопок */ +.spinner-small { + width: 16px; + height: 16px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: white; + animation: spin 1s ease-in-out infinite; + margin-right: 6px; + display: inline-block; + vertical-align: middle; +} \ No newline at end of file