добавление городов для выбора из списка в профиле
This commit is contained in:
parent
0f9db06f25
commit
1977b169df
@ -85,11 +85,15 @@ const userSchema = new mongoose.Schema(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// ... (Middleware pre('save') для хеширования пароля и метод matchPassword остаются без изменений) ...
|
// Middleware pre('save') для хеширования пароля и установки страны по умолчанию
|
||||||
userSchema.pre('save', async function (next) {
|
userSchema.pre('save', async function (next) {
|
||||||
if (!this.isModified('password')) {
|
if (!this.isModified('password')) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
// Если город указан, а страна нет, устанавливаем страну по умолчанию
|
||||||
|
if (this.isModified('location.city') && this.location.city && !this.location.country) {
|
||||||
|
this.location.country = 'Россия';
|
||||||
|
}
|
||||||
const salt = await bcrypt.genSalt(10);
|
const salt = await bcrypt.genSalt(10);
|
||||||
this.password = await bcrypt.hash(this.password, salt);
|
this.password = await bcrypt.hash(this.password, salt);
|
||||||
next();
|
next();
|
||||||
|
11342
src/assets/russian-cities.json
Normal file
11342
src/assets/russian-cities.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -24,6 +24,32 @@
|
|||||||
<option value="other">Другой</option>
|
<option value="other">Другой</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Поле для выбора города -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="editCity" class="form-label">Город:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="editCity"
|
||||||
|
v-model="citySearchQuery"
|
||||||
|
placeholder="Начните вводить город..."
|
||||||
|
@focus="showCityList = true"
|
||||||
|
: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);">
|
||||||
|
<li
|
||||||
|
v-for="city in filteredCities"
|
||||||
|
:key="city.name"
|
||||||
|
class="list-group-item list-group-item-action"
|
||||||
|
@click="selectCity(city.name)"
|
||||||
|
>
|
||||||
|
{{ city.name }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<input type="hidden" v-model="formData.location.city" />
|
||||||
|
</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>
|
||||||
@ -64,10 +90,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, watch } from 'vue';
|
import { ref, onMounted, watch, computed } 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';
|
||||||
|
import russianCities from '@/assets/russian-cities.json'; // Импортируем список городов
|
||||||
|
|
||||||
const { user, fetchUser } = useAuth();
|
const { user, fetchUser } = useAuth();
|
||||||
|
|
||||||
@ -76,7 +103,16 @@
|
|||||||
bio: '',
|
bio: '',
|
||||||
dateOfBirth: '',
|
dateOfBirth: '',
|
||||||
gender: '',
|
gender: '',
|
||||||
|
location: { // Добавляем location
|
||||||
|
city: '',
|
||||||
|
country: 'Россия' // Можно установить по умолчанию, если применимо
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Для выбора города
|
||||||
|
const allCities = ref(russianCities);
|
||||||
|
const citySearchQuery = ref('');
|
||||||
|
const showCityList = ref(false);
|
||||||
|
|
||||||
const profileLoading = ref(false);
|
const profileLoading = ref(false);
|
||||||
const profileErrorMessage = ref('');
|
const profileErrorMessage = ref('');
|
||||||
@ -92,6 +128,35 @@
|
|||||||
const selectedFiles = ref([]); // Массив выбранных файлов
|
const selectedFiles = ref([]); // Массив выбранных файлов
|
||||||
const photoUploadMessages = ref([]); // Массив сообщений о статусе загрузки каждого файла
|
const photoUploadMessages = ref([]); // Массив сообщений о статусе загрузки каждого файла
|
||||||
|
|
||||||
|
const filteredCities = computed(() => {
|
||||||
|
if (!citySearchQuery.value) {
|
||||||
|
return allCities.value;
|
||||||
|
}
|
||||||
|
const query = citySearchQuery.value.toLowerCase();
|
||||||
|
return allCities.value.filter(city =>
|
||||||
|
city.name.toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectCity = (cityName) => {
|
||||||
|
formData.value.location.city = cityName;
|
||||||
|
citySearchQuery.value = cityName; // Обновляем текстовое поле, чтобы показать выбранный город
|
||||||
|
showCityList.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Закрытие списка городов при клике вне его
|
||||||
|
const handleClickOutsideCityList = (event) => {
|
||||||
|
const cityInput = document.getElementById('editCity');
|
||||||
|
const cityDropdown = document.querySelector('.city-dropdown');
|
||||||
|
if (
|
||||||
|
showCityList.value &&
|
||||||
|
cityInput && !cityInput.contains(event.target) &&
|
||||||
|
cityDropdown && !cityDropdown.contains(event.target)
|
||||||
|
) {
|
||||||
|
showCityList.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const initializeFormData = (currentUser) => {
|
const initializeFormData = (currentUser) => {
|
||||||
console.log('[EditProfileForm] Инициализация формы с данными:', currentUser);
|
console.log('[EditProfileForm] Инициализация формы с данными:', currentUser);
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
@ -99,13 +164,28 @@
|
|||||||
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 || '';
|
||||||
// Не очищаем photoUploadMessages здесь, чтобы пользователь видел результаты предыдущих загрузок в сессии
|
if (currentUser.location) {
|
||||||
|
formData.value.location.city = currentUser.location.city || '';
|
||||||
|
citySearchQuery.value = currentUser.location.city || ''; // Инициализируем и поле поиска
|
||||||
|
formData.value.location.country = currentUser.location.country || 'Россия';
|
||||||
|
} else {
|
||||||
|
formData.value.location.city = '';
|
||||||
|
citySearchQuery.value = '';
|
||||||
|
formData.value.location.country = 'Россия';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
console.log('[EditProfileForm] Компонент смонтирован');
|
console.log('[EditProfileForm] Компонент смонтирован');
|
||||||
initializeFormData(user.value);
|
initializeFormData(user.value);
|
||||||
|
document.addEventListener('click', handleClickOutsideCityList);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Добавляем onUnmounted для удаления слушателя
|
||||||
|
import { onUnmounted } from 'vue';
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('click', handleClickOutsideCityList);
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(user, (newUser) => {
|
watch(user, (newUser) => {
|
||||||
@ -125,6 +205,10 @@
|
|||||||
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 в отправляемые данные
|
||||||
|
city: formData.value.location.city,
|
||||||
|
country: formData.value.location.country
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('[EditProfileForm] Данные для обновления профиля:', dataToUpdate);
|
console.log('[EditProfileForm] Данные для обновления профиля:', dataToUpdate);
|
||||||
@ -251,6 +335,7 @@
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
position: relative; /* Для позиционирования выпадающего списка городов */
|
||||||
}
|
}
|
||||||
.img-thumbnail {
|
.img-thumbnail {
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #dee2e6;
|
||||||
@ -270,4 +355,22 @@
|
|||||||
.upload-message.pending, .upload-message.uploading {
|
.upload-message.pending, .upload-message.uploading {
|
||||||
color: orange;
|
color: orange;
|
||||||
}
|
}
|
||||||
|
.city-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%; /* Располагаем под инпутом */
|
||||||
|
left: 0;
|
||||||
|
right: 0; /* Растягиваем по ширине родителя (или инпута) */
|
||||||
|
z-index: 1000;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-top: none; /* Убираем верхнюю границу, т.к. она будет от инпута */
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: white; /* Чтобы список был поверх других элементов */
|
||||||
|
}
|
||||||
|
.city-dropdown .list-group-item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.city-dropdown .list-group-item:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
Loading…
x
Reference in New Issue
Block a user