фикс списка городов и их поиск
This commit is contained in:
parent
1977b169df
commit
09b11b9c4b
@ -25,31 +25,50 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Поле для выбора города -->
|
<!-- Улучшенное поле для выбора города -->
|
||||||
<div class="mb-3">
|
<div class="mb-3 city-select-container">
|
||||||
<label for="editCity" class="form-label">Город:</label>
|
<label for="editCity" class="form-label">Город:</label>
|
||||||
|
<div class="input-group">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
id="editCity"
|
id="editCity"
|
||||||
v-model="citySearchQuery"
|
v-model="citySearchQuery"
|
||||||
placeholder="Начните вводить город..."
|
placeholder="Начните вводить название города..."
|
||||||
@focus="showCityList = true"
|
@focus="showCityList = true"
|
||||||
|
@input="onCitySearch"
|
||||||
:disabled="profileLoading"
|
:disabled="profileLoading"
|
||||||
/>
|
/>
|
||||||
<ul v-if="showCityList && filteredCities.length" class="list-group city-dropdown" style="max-height: 200px; overflow-y: auto; position: absolute; z-index: 1000; width: calc(100% - 2rem);">
|
<span class="input-group-text" @click="clearCitySelection" v-if="formData.location.city">
|
||||||
<li
|
<i class="bi bi-x-circle"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="showCityList && filteredCities.length" class="city-dropdown">
|
||||||
|
<div class="search-results-info" v-if="filteredCities.length">
|
||||||
|
Найдено городов: {{ filteredCities.length }}
|
||||||
|
</div>
|
||||||
|
<div class="city-list-container">
|
||||||
|
<div
|
||||||
v-for="city in filteredCities"
|
v-for="city in filteredCities"
|
||||||
:key="city.name"
|
:key="city.name"
|
||||||
class="list-group-item list-group-item-action"
|
class="city-item"
|
||||||
@click="selectCity(city.name)"
|
@click="selectCity(city)"
|
||||||
>
|
>
|
||||||
{{ city.name }}
|
<div class="city-name">{{ city.name }}</div>
|
||||||
</li>
|
<div class="city-region">{{ city.subject }}</div>
|
||||||
</ul>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="showCityList && citySearchQuery && !filteredCities.length" class="no-results">
|
||||||
|
Городов не найдено
|
||||||
|
</div>
|
||||||
|
<div v-if="formData.location.city" class="selected-city mt-2">
|
||||||
|
<small>Выбранный город: <strong>{{ formData.location.city }}</strong></small>
|
||||||
|
</div>
|
||||||
<input type="hidden" v-model="formData.location.city" />
|
<input type="hidden" v-model="formData.location.city" />
|
||||||
</div>
|
</div>
|
||||||
<!-- Конец поля для выбора города -->
|
<!-- Конец улучшенного поля для выбора города -->
|
||||||
|
|
||||||
<div v-if="profileSuccessMessage" class="alert alert-success">{{ profileSuccessMessage }}</div>
|
<div v-if="profileSuccessMessage" class="alert alert-success">{{ profileSuccessMessage }}</div>
|
||||||
<div v-if="profileErrorMessage" class="alert alert-danger">{{ profileErrorMessage }}</div>
|
<div v-if="profileErrorMessage" class="alert alert-danger">{{ profileErrorMessage }}</div>
|
||||||
@ -90,7 +109,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, watch, computed } from 'vue';
|
import { ref, onMounted, watch, computed, nextTick } from 'vue';
|
||||||
import { useAuth } from '@/auth';
|
import { useAuth } from '@/auth';
|
||||||
import api from '@/services/api';
|
import api from '@/services/api';
|
||||||
import imageCompression from 'browser-image-compression';
|
import imageCompression from 'browser-image-compression';
|
||||||
@ -103,16 +122,18 @@
|
|||||||
bio: '',
|
bio: '',
|
||||||
dateOfBirth: '',
|
dateOfBirth: '',
|
||||||
gender: '',
|
gender: '',
|
||||||
location: { // Добавляем location
|
location: {
|
||||||
city: '',
|
city: '',
|
||||||
country: 'Россия' // Можно установить по умолчанию, если применимо
|
country: 'Россия'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Для выбора города
|
// Для выбора города
|
||||||
const allCities = ref(russianCities);
|
const allCities = ref([]);
|
||||||
const citySearchQuery = ref('');
|
const citySearchQuery = ref('');
|
||||||
const showCityList = ref(false);
|
const showCityList = ref(false);
|
||||||
|
const selectedCity = ref(null);
|
||||||
|
const searchTimeout = ref(null);
|
||||||
|
|
||||||
const profileLoading = ref(false);
|
const profileLoading = ref(false);
|
||||||
const profileErrorMessage = ref('');
|
const profileErrorMessage = ref('');
|
||||||
@ -128,31 +149,82 @@
|
|||||||
const selectedFiles = ref([]); // Массив выбранных файлов
|
const selectedFiles = ref([]); // Массив выбранных файлов
|
||||||
const photoUploadMessages = ref([]); // Массив сообщений о статусе загрузки каждого файла
|
const photoUploadMessages = ref([]); // Массив сообщений о статусе загрузки каждого файла
|
||||||
|
|
||||||
|
// Улучшенный поиск с дебаунсингом
|
||||||
|
const onCitySearch = () => {
|
||||||
|
if (searchTimeout.value) {
|
||||||
|
clearTimeout(searchTimeout.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если поле поиска пустое и нет выбранного города, сбрасываем город
|
||||||
|
if (!citySearchQuery.value && formData.value.location.city) {
|
||||||
|
formData.value.location.city = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показываем список только если есть текст для поиска
|
||||||
|
if (citySearchQuery.value) {
|
||||||
|
showCityList.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем небольшую задержку для улучшения производительности
|
||||||
|
searchTimeout.value = setTimeout(() => {
|
||||||
|
// Если значение в поле поиска не соответствует выбранному городу,
|
||||||
|
// считаем, что пользователь начал новый поиск
|
||||||
|
if (selectedCity.value && citySearchQuery.value !== selectedCity.value.name) {
|
||||||
|
selectedCity.value = null;
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
const filteredCities = computed(() => {
|
const filteredCities = computed(() => {
|
||||||
if (!citySearchQuery.value) {
|
if (!citySearchQuery.value) {
|
||||||
return allCities.value;
|
return allCities.value.slice(0, 10); // Возвращаем первые 10 городов, если поле пустое
|
||||||
}
|
}
|
||||||
const query = citySearchQuery.value.toLowerCase();
|
|
||||||
return allCities.value.filter(city =>
|
const query = citySearchQuery.value.toLowerCase().trim();
|
||||||
|
|
||||||
|
// Сначала находим города, начинающиеся с запроса
|
||||||
|
const startsWithMatch = allCities.value.filter(city =>
|
||||||
|
city.name.toLowerCase().startsWith(query)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Затем города, содержащие запрос где-то в середине
|
||||||
|
const containsMatch = allCities.value.filter(city =>
|
||||||
|
!city.name.toLowerCase().startsWith(query) &&
|
||||||
city.name.toLowerCase().includes(query)
|
city.name.toLowerCase().includes(query)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Объединяем результаты, сначала точные совпадения, затем частичные
|
||||||
|
return [...startsWithMatch, ...containsMatch].slice(0, 15);
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectCity = (cityName) => {
|
const selectCity = (city) => {
|
||||||
formData.value.location.city = cityName;
|
selectedCity.value = city;
|
||||||
citySearchQuery.value = cityName; // Обновляем текстовое поле, чтобы показать выбранный город
|
formData.value.location.city = city.name;
|
||||||
|
citySearchQuery.value = city.name;
|
||||||
showCityList.value = false;
|
showCityList.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Закрытие списка городов при клике вне его
|
const clearCitySelection = () => {
|
||||||
|
selectedCity.value = null;
|
||||||
|
formData.value.location.city = '';
|
||||||
|
citySearchQuery.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Улучшенная логика обработки кликов вне списка
|
||||||
const handleClickOutsideCityList = (event) => {
|
const handleClickOutsideCityList = (event) => {
|
||||||
const cityInput = document.getElementById('editCity');
|
const cityInput = document.getElementById('editCity');
|
||||||
const cityDropdown = document.querySelector('.city-dropdown');
|
const cityDropdown = document.querySelector('.city-dropdown');
|
||||||
if (
|
|
||||||
showCityList.value &&
|
if (!cityInput || !event.target) return;
|
||||||
cityInput && !cityInput.contains(event.target) &&
|
|
||||||
cityDropdown && !cityDropdown.contains(event.target)
|
const clickedOnInput = cityInput.contains(event.target);
|
||||||
) {
|
const clickedOnDropdown = cityDropdown && cityDropdown.contains(event.target);
|
||||||
|
|
||||||
|
if (showCityList.value && !clickedOnInput && !clickedOnDropdown) {
|
||||||
|
// Если есть выбранный город, восстанавливаем его имя в поле поиска
|
||||||
|
if (selectedCity.value) {
|
||||||
|
citySearchQuery.value = selectedCity.value.name;
|
||||||
|
}
|
||||||
showCityList.value = false;
|
showCityList.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -164,21 +236,33 @@
|
|||||||
formData.value.bio = currentUser.bio || '';
|
formData.value.bio = currentUser.bio || '';
|
||||||
formData.value.dateOfBirth = currentUser.dateOfBirth ? new Date(currentUser.dateOfBirth).toISOString().split('T')[0] : '';
|
formData.value.dateOfBirth = currentUser.dateOfBirth ? new Date(currentUser.dateOfBirth).toISOString().split('T')[0] : '';
|
||||||
formData.value.gender = currentUser.gender || '';
|
formData.value.gender = currentUser.gender || '';
|
||||||
|
|
||||||
if (currentUser.location) {
|
if (currentUser.location) {
|
||||||
formData.value.location.city = currentUser.location.city || '';
|
formData.value.location.city = currentUser.location.city || '';
|
||||||
citySearchQuery.value = currentUser.location.city || ''; // Инициализируем и поле поиска
|
citySearchQuery.value = currentUser.location.city || '';
|
||||||
formData.value.location.country = currentUser.location.country || 'Россия';
|
formData.value.location.country = currentUser.location.country || 'Россия';
|
||||||
} else {
|
|
||||||
formData.value.location.city = '';
|
// Если есть выбранный город, находим его в списке
|
||||||
citySearchQuery.value = '';
|
if (currentUser.location.city) {
|
||||||
formData.value.location.country = 'Россия';
|
selectedCity.value = allCities.value.find(city =>
|
||||||
|
city.name === currentUser.location.city
|
||||||
|
) || null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
console.log('[EditProfileForm] Компонент смонтирован');
|
console.log('[EditProfileForm] Компонент смонтирован');
|
||||||
|
|
||||||
|
// Загружаем список городов и сортируем по имени
|
||||||
|
allCities.value = russianCities.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
// Инициализируем данные формы
|
||||||
|
nextTick(() => {
|
||||||
initializeFormData(user.value);
|
initializeFormData(user.value);
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener('click', handleClickOutsideCityList);
|
document.addEventListener('click', handleClickOutsideCityList);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -205,7 +289,7 @@
|
|||||||
bio: formData.value.bio,
|
bio: formData.value.bio,
|
||||||
dateOfBirth: formData.value.dateOfBirth || null,
|
dateOfBirth: formData.value.dateOfBirth || null,
|
||||||
gender: formData.value.gender || null,
|
gender: formData.value.gender || null,
|
||||||
location: { // Включаем location в отправляемые данные
|
location: {
|
||||||
city: formData.value.location.city,
|
city: formData.value.location.city,
|
||||||
country: formData.value.location.country
|
country: formData.value.location.country
|
||||||
}
|
}
|
||||||
@ -330,47 +414,101 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@import url('https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css');
|
||||||
|
|
||||||
.edit-profile-form {
|
.edit-profile-form {
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
position: relative; /* Для позиционирования выпадающего списка городов */
|
|
||||||
}
|
}
|
||||||
.img-thumbnail {
|
|
||||||
border: 1px solid #dee2e6;
|
/* Стили для выбора города */
|
||||||
padding: 0.25rem;
|
.city-select-container {
|
||||||
background-color: #fff;
|
position: relative;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.city-dropdown {
|
.city-dropdown {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 100%; /* Располагаем под инпутом */
|
top: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0; /* Растягиваем по ширине родителя (или инпута) */
|
right: 0;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
border: 1px solid #ced4da;
|
background-color: white;
|
||||||
border-top: none; /* Убираем верхнюю границу, т.к. она будет от инпута */
|
border: 1px solid #dee2e6;
|
||||||
max-height: 200px;
|
border-radius: 0 0 5px 5px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||||
|
max-height: 350px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-results-info {
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #6c757d;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city-list-container {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background-color: white; /* Чтобы список был поверх других элементов */
|
max-height: 300px;
|
||||||
|
padding: 5px 0;
|
||||||
}
|
}
|
||||||
.city-dropdown .list-group-item {
|
|
||||||
|
.city-item {
|
||||||
|
padding: 8px 15px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.city-dropdown .list-group-item:hover {
|
|
||||||
|
.city-item:hover {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city-name {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.city-region {
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-city {
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-results {
|
||||||
|
padding: 10px 15px;
|
||||||
|
color: #dc3545;
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 5px 5px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||||
|
background-color: white;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Кнопка очистки */
|
||||||
|
.input-group-text {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-text:hover {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ...existing code... */
|
||||||
</style>
|
</style>
|
Loading…
x
Reference in New Issue
Block a user