From c884b65208c3b09c6622064bbcd36fb58c9bac85 Mon Sep 17 00:00:00 2001 From: 107 <107@DESKTOP-UP8U7M2> Date: Fri, 23 May 2025 13:33:15 +0700 Subject: [PATCH] =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D1=84=D0=BE=D1=80=D0=BC=D1=8B=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/EditProfileForm.vue | 1209 ---------------------------- src/views/ProfileView.vue | 893 ++++++++++++++------ 2 files changed, 655 insertions(+), 1447 deletions(-) delete mode 100644 src/components/EditProfileForm.vue diff --git a/src/components/EditProfileForm.vue b/src/components/EditProfileForm.vue deleted file mode 100644 index 7b92209..0000000 --- a/src/components/EditProfileForm.vue +++ /dev/null @@ -1,1209 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/views/ProfileView.vue b/src/views/ProfileView.vue index 9f71b84..df54438 100644 --- a/src/views/ProfileView.vue +++ b/src/views/ProfileView.vue @@ -101,24 +101,115 @@

Основная информация

+
+ +
+ +
+ + {{ profileActionError }} +
+
+ + {{ profileActionSuccess }} +
+ +
- - {{ profileData.dateOfBirth ? formatDate(profileData.dateOfBirth) : 'Не указана' }} + + {{ profileData.name || 'Не указано' }} +
- - {{ getGenderText(profileData.gender) }} + + {{ profileData.dateOfBirth ? formatDate(profileData.dateOfBirth) : 'Не указана' }} +
- - {{ profileData.location?.city || 'Не указан' }} + + {{ getGenderText(profileData.gender) }} + +
+
+ + {{ profileData.location?.city || 'Не указан' }} +
+ + +
+
+ {{ city }} +
+
+
- - {{ profileData.bio || 'Расскажите о себе...' }} + + {{ profileData.bio || 'Расскажите о себе...' }} +
@@ -195,14 +286,8 @@
- - - + + { @@ -285,10 +384,27 @@ const mainPhoto = computed(() => { // Methods const toggleEditMode = () => { isEditMode.value = !isEditMode.value; + if (isEditMode.value) { - nextTick(() => { - scrollToEdit(); - }); + // При входе в режим редактирования копируем данные профиля в редактируемый объект + editableProfileData.value = { + name: profileData.value.name || '', + bio: profileData.value.bio || '', + dateOfBirth: profileData.value.dateOfBirth || '', + gender: profileData.value.gender || '', + location: { + city: profileData.value.location?.city || '', + // Можно добавить другие поля местоположения, если они есть + } + }; + + // Устанавливаем поисковый запрос города + if (profileData.value.location?.city) { + citySearchQuery.value = profileData.value.location.city; + } + } else { + // При выходе из режима редактирования сбрасываем ошибки и сообщения + clearProfileMessages(); } }; @@ -299,11 +415,7 @@ const scrollToPhotos = () => { } }; -const scrollToEdit = () => { - if (editFormRef.value && editFormRef.value.$el) { - editFormRef.value.$el.scrollIntoView({ behavior: 'smooth', block: 'start' }); - } -}; + const triggerPhotoUpload = () => { if (fileInput.value) { @@ -346,13 +458,8 @@ const handlePhotoSelect = async (event) => { return; } - // Если активна форма редактирования, используем её метод - if (isEditMode.value && editFormRef.value && editFormRef.value.handlePhotoUpload) { - await editFormRef.value.handlePhotoUpload(Array.from(files)); - } else { - // Иначе обрабатываем загрузку фото здесь - await uploadPhotos(Array.from(files)); - } + // Обрабатываем загрузку фото + await uploadPhotos(Array.from(files)); // Очищаем input event.target.value = ''; @@ -636,7 +743,99 @@ const executeDeletePhoto = async () => { } }; -// Watchers +const saveProfileChanges = async () => { + profileLoading.value = true; + clearProfileMessages(); + + try { + console.log('[ProfileView] Сохранение данных профиля:', editableProfileData.value); + + // Подготавливаем данные для отправки на сервер + const dataToUpdate = { ...editableProfileData.value }; + + // Обрабатываем дату рождения - если это строка с датой, преобразуем в формат ISO + if (dataToUpdate.dateOfBirth) { + // dateOfBirth уже в нужном формате из input type="date" + console.log('[ProfileView] Дата рождения для отправки:', dataToUpdate.dateOfBirth); + } + + const response = await api.updateUserProfile(dataToUpdate); + console.log('[ProfileView] Ответ от сервера (профиль):', response.data); + + // Обновляем данные в профиле + await fetchUser(); + + profileActionSuccess.value = 'Профиль успешно обновлен!'; + + // Автоматически скрываем сообщение через 3 секунды + setTimeout(() => { + profileActionSuccess.value = ''; + }, 3000); + } catch (err) { + console.error('[ProfileView] Ошибка при обновлении профиля:', err); + profileActionError.value = err.response?.data?.message || + 'Произошла ошибка при обновлении профиля. Пожалуйста, попробуйте позже.'; + } finally { + profileLoading.value = false; + } +}; + +// Методы для работы с городами +const loadCities = async () => { + if (cities.length > 0) return; + + try { + const response = await fetch('/src/assets/russian-cities.json'); + const data = await response.json(); + cities = data; + console.log('[ProfileView] Загружен список городов:', cities.length); + } catch (err) { + console.error('[ProfileView] Ошибка при загрузке списка городов:', err); + } +}; + +const onCitySearch = () => { + if (!cities.length) { + loadCities(); + return; + } + + if (citySearchQuery.value.length < 2) { + filteredCities.value = []; + return; + } + + const query = citySearchQuery.value.toLowerCase().trim(); + filteredCities.value = cities + .filter(city => city.toLowerCase().includes(query)) + .slice(0, 10); // Ограничиваем количество результатов +}; + +const selectCity = (city) => { + citySearchQuery.value = city; + + if (!editableProfileData.value.location) { + editableProfileData.value.location = {}; + } + + editableProfileData.value.location.city = city; + showCityList.value = false; +}; + +const clearCitySelection = () => { + citySearchQuery.value = ''; + + if (editableProfileData.value.location) { + editableProfileData.value.location.city = ''; + } +}; + +const clearProfileMessages = () => { + profileActionError.value = ''; + profileActionSuccess.value = ''; +}; + + watch(authUserFromStore, (newUser) => { if (newUser) { profileData.value = { ...newUser }; @@ -644,6 +843,12 @@ watch(authUserFromStore, (newUser) => { } }, { immediate: true, deep: true }); +watch(profileData, (newData) => { + if (newData) { + editableProfileData.value = { ...newData }; + } +}, { immediate: true, deep: true }); + // Lifecycle onMounted(async () => { if (!authUserFromStore.value || Object.keys(authUserFromStore.value).length === 0) { @@ -1035,6 +1240,7 @@ onMounted(async () => { .card-header h3 { margin: 0; color: #495057; + margin-bottom: 1rem; font-size: 1.2rem; display: flex; align-items: center; @@ -1079,117 +1285,143 @@ onMounted(async () => { line-height: 1.6; } -/* Photo Grid */ -.add-photo-btn { - background: linear-gradient(45deg, #667eea, #764ba2); - color: white; - border: none; - padding: 0.5rem 1rem; - border-radius: 50px; - font-size: 0.8rem; - font-weight: 600; - cursor: pointer; - transition: all 0.3s ease; - display: flex; - align-items: center; - gap: 0.5rem; -} - -.add-photo-btn:hover { - transform: translateY(-2px); - 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)); - gap: 1rem; -} - -.photo-item { - position: relative; - border-radius: 15px; - overflow: hidden; - background: #f8f9fa; - transition: transform 0.3s ease; -} - -.photo-item:hover { - transform: translateY(-5px); -} - -.photo-item.main-photo { - border: 3px solid #667eea; -} - -.photo-image { +/* Стили формы */ +.form-input { width: 100%; - height: 200px; - object-fit: cover; - display: block; + padding: 0.75rem 1rem; + border-radius: 8px; + border: 1px solid #dee2e6; + background: #f8f9fa; + font-size: 0.95rem; + transition: all 0.3s ease; + color: #495057; } -.main-badge { +.form-input:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); + background: white; +} + +.form-input:disabled { + opacity: 0.7; + cursor: not-allowed; + background: #e9ecef; +} + +.form-textarea { + min-height: 100px; + resize: vertical; + line-height: 1.5; +} + +.form-select { + appearance: none; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 0.75rem center; + background-size: 16px 12px; +} + +/* Стили для выпадающего списка городов */ +.city-input-wrapper { + position: relative; + width: 100%; +} + +.city-dropdown { position: absolute; - top: 10px; - left: 10px; - background: linear-gradient(45deg, #ffc107, #fd7e14); - color: white; - padding: 0.25rem 0.5rem; - border-radius: 50px; - font-size: 0.7rem; - font-weight: 600; + top: 100%; + left: 0; + right: 0; + max-height: 250px; + overflow-y: auto; + background: white; + border: 1px solid #dee2e6; + border-radius: 8px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); + z-index: 100; +} + +.city-option { + padding: 0.75rem 1rem; + cursor: pointer; + color: #495057; + transition: all 0.2s ease; +} + +.city-option:hover { + background: rgba(102, 126, 234, 0.1); + color: #667eea; +} + +.city-clear-btn { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: #6c757d; + cursor: pointer; + padding: 5px; display: flex; align-items: center; - gap: 0.25rem; + justify-content: center; } -.photo-actions { - position: absolute; - bottom: 10px; - right: 10px; +.city-clear-btn:hover { + color: #dc3545; +} + +.card-header h3 { + margin-bottom: 0 !important; +} +/* Loading State */ +.loading-section { display: flex; - gap: 0.5rem; - opacity: 1; - transition: opacity 0.3s ease; - background: rgba(0, 0, 0, 0.7); - padding: 0.5rem; - border-radius: 10px; - z-index: 10; - pointer-events: auto; + justify-content: center; + align-items: center; + min-height: 400px; + color: white; } -.photo-item:hover .photo-actions { - opacity: 1; -} - -/* Empty States */ -.empty-photos { +.loading-spinner { text-align: center; - padding: 3rem; - color: #6c757d; } -.empty-photos i { - font-size: 4rem; - margin-bottom: 1rem; - opacity: 0.5; +.spinner { + width: 50px; + height: 50px; + border: 4px solid rgba(255, 255, 255, 0.3); + border-left: 4px solid white; + border-radius: 50%; + animation: spin 1s linear infinite; + margin: 0 auto 1rem; } -.empty-photos h4 { - margin-bottom: 1rem; +.spinner-small { + width: 16px; + height: 16px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-left: 2px solid white; + border-radius: 50%; + animation: spin 0.8s linear infinite; + display: inline-block; } -.no-data-section { +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Error Section */ +.error-section { padding: 4rem 0; } -.no-data-card { +.error-card { background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); border-radius: 20px; @@ -1198,163 +1430,348 @@ onMounted(async () => { box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); } -.no-data-card i { +.error-card i { font-size: 3rem; - color: #6c757d; + color: #dc3545; margin-bottom: 1rem; } -/* Alerts */ -.alert { - padding: 1rem 1.5rem; - border-radius: 10px; - margin: 1rem 0; +.error-card h3 { + color: #495057; + margin-bottom: 1rem; +} + +.retry-btn { + background: linear-gradient(45deg, #667eea, #764ba2); + color: white; + border: none; + padding: 0.75rem 2rem; + border-radius: 50px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.retry-btn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); +} + +/* Main Content */ +.main-content { + padding: 2rem 0 4rem; +} + +.profile-layout { + display: grid; + grid-template-columns: 350px 1fr; + gap: 2rem; + align-items: start; +} + +/* Profile Card */ +.profile-card { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-radius: 20px; + padding: 2rem; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + position: sticky; + top: 2rem; +} + +.avatar-section { + text-align: center; + margin-bottom: 2rem; +} + +.avatar-container { + position: relative; + display: inline-block; + margin-bottom: 1rem; +} + +.avatar-image { + width: 120px; + height: 120px; + border-radius: 50%; + object-fit: cover; + border: 4px solid white; + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); +} + +.avatar-placeholder { + width: 120px; + height: 120px; + border-radius: 50%; + background: linear-gradient(45deg, #e9ecef, #f8f9fa); display: flex; align-items: center; - gap: 0.5rem; - font-weight: 500; + justify-content: center; + border: 4px solid white; + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); } -.alert.error { - background: rgba(220, 53, 69, 0.1); - color: #721c24; - border: 1px solid rgba(220, 53, 69, 0.2); +.avatar-placeholder i { + font-size: 3rem; + color: #6c757d; } -.alert.success { - background: rgba(40, 167, 69, 0.1); - color: #155724; - 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; +.avatar-overlay { + position: absolute; top: 0; left: 0; right: 0; bottom: 0; - background: rgba(0, 0, 0, 0.5); + background: rgba(0, 0, 0, 0.7); + border-radius: 50%; display: flex; align-items: center; justify-content: center; - z-index: 1000; - backdrop-filter: blur(5px); + opacity: 1; + transition: opacity 0.3s ease; + z-index: 5; } -.delete-modal { - background: white; +.avatar-container:hover .avatar-overlay { + opacity: 1; +} + +.change-avatar-btn { + background: none; + border: none; + color: white; + font-size: 1.5rem; + cursor: pointer; +} + +.user-name { + font-size: 1.5rem; + font-weight: 700; + color: #2c3e50; + margin: 0 0 0.5rem 0; +} + +.user-email { + color: #6c757d; + margin: 0 0 1rem 0; +} + +.user-badges { + display: flex; + gap: 0.5rem; + justify-content: center; + flex-wrap: wrap; +} + +.badge { + padding: 0.25rem 0.75rem; + border-radius: 50px; + font-size: 0.75rem; + font-weight: 600; +} + +.badge.verified { + background: linear-gradient(45deg, #28a745, #20c997); + color: white; +} + +.badge.member-since { + background: #e9ecef; + color: #495057; +} + +/* Stats Section */ +.stats-section { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; + margin-top: 2rem; + padding-top: 2rem; + border-top: 1px solid #e9ecef; +} + +.stat-item { + text-align: center; +} + +.stat-value { + display: block; + font-size: 1.5rem; + font-weight: 700; + color: #667eea; +} + +.stat-label { + font-size: 0.8rem; + color: #6c757d; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Details Section */ +.details-section { + display: flex; + flex-direction: column; + gap: 2rem; +} + +.info-card { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); border-radius: 20px; - max-width: 500px; - width: 90%; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); overflow: hidden; } -.modal-header { +.card-header { padding: 1.5rem 2rem; - background: #f8f9fa; + background: linear-gradient(45deg, #f8f9fa, #e9ecef); border-bottom: 1px solid #dee2e6; display: flex; justify-content: space-between; align-items: center; } -.modal-header h3 { +.card-header h3 { margin: 0; color: #495057; + margin-bottom: 1rem; + font-size: 1.2rem; + display: flex; + align-items: center; + gap: 0.5rem; } -.close-btn { - background: none; - border: none; - font-size: 1.5rem; - cursor: pointer; - color: #6c757d; - padding: 0; -} - -.modal-content { +.card-content { padding: 2rem; } -.modal-actions { - padding: 1.5rem 2rem; - background: #f8f9fa; - border-top: 1px solid #dee2e6; +.info-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; +} + +.info-item { display: flex; - gap: 1rem; - justify-content: flex-end; + flex-direction: column; + gap: 0.5rem; } -/* Responsive Design */ -@media (max-width: 768px) { - .profile-layout { - grid-template-columns: 1fr; - gap: 1rem; - } - - .profile-card { - position: static; - } - - .header-content { - flex-direction: column; - text-align: center; - } - - .profile-title h1 { - font-size: 2rem; - } - - .info-grid { - grid-template-columns: 1fr; - } - - .stats-section { - grid-template-columns: repeat(3, 1fr); - } - - .photo-grid { - grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); - } +.info-item.full-width { + grid-column: 1 / -1; } -@media (max-width: 480px) { - .container { - padding: 0 15px; - } - - .profile-header { - padding: 1.5rem 0; - } - - .profile-title h1 { - font-size: 1.8rem; - } - - .card-content, - .modal-content { - padding: 1.5rem; - } +.info-item label { + font-weight: 600; + color: #495057; + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; } -/* 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; +.info-item span { + color: #6c757d; + font-size: 1rem; +} + +.bio-text { + font-style: italic; + line-height: 1.6; +} + +/* Стили формы */ +.form-input { + width: 100%; + padding: 0.75rem 1rem; + border-radius: 8px; + border: 1px solid #dee2e6; + background: #f8f9fa; + font-size: 0.95rem; + transition: all 0.3s ease; + color: #495057; +} + +.form-input:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); + background: white; +} + +.form-input:disabled { + opacity: 0.7; + cursor: not-allowed; + background: #e9ecef; +} + +.form-textarea { + min-height: 100px; + resize: vertical; + line-height: 1.5; +} + +.form-select { + appearance: none; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 0.75rem center; + background-size: 16px 12px; +} + +/* Стили для выпадающего списка городов */ +.city-input-wrapper { + position: relative; + width: 100%; +} + +.city-dropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + max-height: 250px; + overflow-y: auto; + background: white; + border: 1px solid #dee2e6; + border-radius: 8px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); + z-index: 100; +} + +.city-option { + padding: 0.75rem 1rem; + cursor: pointer; + color: #495057; + transition: all 0.2s ease; +} + +.city-option:hover { + background: rgba(102, 126, 234, 0.1); + color: #667eea; +} + +.city-clear-btn { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: #6c757d; + cursor: pointer; + padding: 5px; + display: flex; + align-items: center; + justify-content: center; +} + +.city-clear-btn:hover { + color: #dc3545; +} + +.card-header h3 { + margin-bottom: 0 !important; } \ No newline at end of file