new file: main_dc/yalarba/easySite/easySite/app/components/ObjectCard.vue
new file: main_dc/yalarba/easySite/easySite/app/components/ObjectForm.vue deleted: main_dc/yalarba/easySite/easySite/app/components/forms/ObjectForm.vue modified: main_dc/yalarba/easySite/easySite/app/pages/objects/[id]/edit.vue modified: main_dc/yalarba/easySite/easySite/app/pages/objects/[id]/index.vue modified: main_dc/yalarba/easySite/easySite/app/pages/objects/create.vue modified: main_dc/yalarba/easySite/easySite/app/pages/objects/index.vue modified: main_dc/yalarba/easySite/easySite/app/pages/objects/my-objects.vue deleted: main_dc/yalarba/easySite/easySite/app/pages/objects/search.vue modified: main_dc/yalarba/easySite/easySite/app/pages/profile/edit.vue add pages for object, objectsSearch, editObject, createObject
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<!-- components/ObjectCard.vue -->
|
||||
<template>
|
||||
<div class="card cursor-pointer" @click="$emit('click')">
|
||||
<div class="relative">
|
||||
<img
|
||||
:src="object.image"
|
||||
:alt="object.title"
|
||||
class="w-full h-48 object-cover"
|
||||
>
|
||||
<div class="absolute top-2 right-2">
|
||||
<span class="badge badge-primary">
|
||||
{{ getTypeLabel(object.type) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<h3 class="text-lg font-semibold mb-2">{{ object.title }}</h3>
|
||||
<p class="text-gray-600 text-sm mb-3 line-clamp-2">
|
||||
{{ object.description }}
|
||||
</p>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-1">
|
||||
<span class="text-yellow-500">⭐</span>
|
||||
<span class="text-sm font-medium">{{ object.rating }}</span>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="font-bold text-primary-600">
|
||||
{{ formatPrice(object.price) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">за ночь</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 flex items-center text-sm text-gray-500">
|
||||
<span class="mr-2">📍</span>
|
||||
<span>{{ object.city }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface ObjectItem {
|
||||
id: number
|
||||
title: string
|
||||
type: string
|
||||
city: string
|
||||
price: number
|
||||
rating: number
|
||||
image: string
|
||||
description: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
object: ObjectItem
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
defineEmits<{
|
||||
click: []
|
||||
}>()
|
||||
|
||||
const getTypeLabel = (type: string) => {
|
||||
const types: Record<string, string> = {
|
||||
hotel: 'Отель',
|
||||
sanatorium: 'Санаторий',
|
||||
guest_house: 'Гостевой дом',
|
||||
tour: 'Тур',
|
||||
excursion: 'Экскурсия'
|
||||
}
|
||||
return types[type] || type
|
||||
}
|
||||
|
||||
const formatPrice = (price: number) => {
|
||||
return new Intl.NumberFormat('ru-RU', {
|
||||
style: 'currency',
|
||||
currency: 'RUB',
|
||||
minimumFractionDigits: 0
|
||||
}).format(price)
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,174 @@
|
||||
<!-- components/ObjectForm.vue -->
|
||||
<template>
|
||||
<form @submit.prevent="handleSubmit" class="space-y-6">
|
||||
<!-- Основная информация -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="text-lg font-semibold">Основная информация</h3>
|
||||
</div>
|
||||
<div class="card-body space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Название объекта *</label>
|
||||
<input v-model="formData.title" type="text" class="form-input" required
|
||||
placeholder="Введите название">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Тип объекта *</label>
|
||||
<select v-model="formData.type" class="form-select" required>
|
||||
<option value="">Выберите тип</option>
|
||||
<option value="hotel">Гостиница</option>
|
||||
<option value="sanatorium">Санаторий</option>
|
||||
<option value="guest_house">Гостевой дом</option>
|
||||
<option value="tour">Тур</option>
|
||||
<option value="excursion">Экскурсия</option>
|
||||
<option value="restaurant">Ресторан</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Описание *</label>
|
||||
<textarea v-model="formData.description" class="form-input" rows="4" required
|
||||
placeholder="Подробное описание объекта"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Местоположение -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="text-lg font-semibold">Местоположение</h3>
|
||||
</div>
|
||||
<div class="card-body space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Город *</label>
|
||||
<input v-model="formData.city" type="text" class="form-input" required placeholder="Город">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Адрес *</label>
|
||||
<input v-model="formData.address" type="text" class="form-input" required
|
||||
placeholder="Полный адрес">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Цены и контакты -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="text-lg font-semibold">Цены и контакты</h3>
|
||||
</div>
|
||||
<div class="card-body space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Цена за ночь/услугу *</label>
|
||||
<input v-model="formData.price" type="number" class="form-input" required placeholder="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Телефон *</label>
|
||||
<input v-model="formData.contact.phone" type="tel" class="form-input" required
|
||||
placeholder="+7 (XXX) XXX-XX-XX">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Email</label>
|
||||
<input v-model="formData.contact.email" type="email" class="form-input"
|
||||
placeholder="email@example.com">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Удобства -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="text-lg font-semibold">Удобства и услуги</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||||
<label v-for="amenity in availableAmenities" :key="amenity" class="flex items-center space-x-2">
|
||||
<input type="checkbox" :value="amenity" v-model="formData.amenities"
|
||||
class="rounded border-gray-300">
|
||||
<span class="text-sm">{{ amenity }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Кнопки действий -->
|
||||
<div class="flex gap-4 justify-end">
|
||||
<button type="button" @click="$emit('cancel')" class="btn btn-outline" :disabled="loading">
|
||||
Отмена
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" :disabled="loading">
|
||||
<span v-if="loading">Сохранение...</span>
|
||||
<span v-else>{{ props.object ? 'Обновить' : 'Создать' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface ObjectFormData {
|
||||
title: string
|
||||
type: string
|
||||
description: string
|
||||
city: string
|
||||
address: string
|
||||
price: number
|
||||
images: string[]
|
||||
amenities: string[]
|
||||
contact: {
|
||||
phone: string
|
||||
email: string
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
object?: ObjectFormData | null
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
object: null,
|
||||
loading: false
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [formData: ObjectFormData]
|
||||
cancel: []
|
||||
}>()
|
||||
|
||||
const availableAmenities = [
|
||||
'Wi-Fi', 'Парковка', 'Бассейн', 'СПА', 'Завтрак',
|
||||
'Кондиционер', 'Трансфер', 'Экскурсии', 'Баня', 'Ресторан'
|
||||
]
|
||||
|
||||
const formData = reactive<ObjectFormData>({
|
||||
title: '',
|
||||
type: '',
|
||||
description: '',
|
||||
city: '',
|
||||
address: '',
|
||||
price: 0,
|
||||
images: [],
|
||||
amenities: [],
|
||||
contact: {
|
||||
phone: '',
|
||||
email: ''
|
||||
}
|
||||
})
|
||||
|
||||
// Заполнение формы данными при редактировании
|
||||
watch(() => props.object, (newObject) => {
|
||||
if (newObject) {
|
||||
Object.assign(formData, newObject)
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
const handleSubmit = () => {
|
||||
emit('submit', { ...formData })
|
||||
}
|
||||
</script>
|
||||
@@ -1,234 +0,0 @@
|
||||
<template>
|
||||
<form @submit.prevent="handleSubmit" class="space-y-8">
|
||||
<!-- Основная информация -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="text-xl font-semibold">Основная информация</h2>
|
||||
</div>
|
||||
<div class="card-body space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Название объекта *</label>
|
||||
<input v-model="form.title" type="text" class="form-input"
|
||||
placeholder="Например: Отель 'Морской бриз'" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Тип объекта *</label>
|
||||
<select v-model="form.type" class="form-select" required>
|
||||
<option value="">Выберите тип</option>
|
||||
<option value="hotel">Отель</option>
|
||||
<option value="apartment">Апартаменты</option>
|
||||
<option value="villa">Вилла</option>
|
||||
<option value="camping">Кемпинг</option>
|
||||
<option value="restaurant">Ресторан</option>
|
||||
<option value="attraction">Достопримечательность</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Описание *</label>
|
||||
<textarea v-model="form.description" rows="4" class="form-input"
|
||||
placeholder="Подробное описание объекта, его преимуществ и особенностей..." required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Местоположение -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="text-xl font-semibold">Местоположение</h2>
|
||||
</div>
|
||||
<div class="card-body space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Страна *</label>
|
||||
<input v-model="form.country" type="text" class="form-input" placeholder="Россия" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Город *</label>
|
||||
<input v-model="form.city" type="text" class="form-input" placeholder="Москва" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Категория *</label>
|
||||
<select v-model="form.category" class="form-select" required>
|
||||
<option value="">Выберите категорию</option>
|
||||
<option value="accommodation">Проживание</option>
|
||||
<option value="food">Питание</option>
|
||||
<option value="entertainment">Развлечения</option>
|
||||
<option value="culture">Культура</option>
|
||||
<option value="nature">Природа</option>
|
||||
<option value="shopping">Шоппинг</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Адрес *</label>
|
||||
<input v-model="form.address" type="text" class="form-input" placeholder="ул. Примерная, 123"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Цены и услуги -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="text-xl font-semibold">Цены и услуги</h2>
|
||||
</div>
|
||||
<div class="card-body space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Цена *</label>
|
||||
<input v-model="form.price" type="number" class="form-input" placeholder="5000" min="0"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Единица измерения цены *</label>
|
||||
<select v-model="form.priceUnit" class="form-select" required>
|
||||
<option value="">Выберите единицу</option>
|
||||
<option value="per_night">За ночь</option>
|
||||
<option value="per_person">За человека</option>
|
||||
<option value="fixed">Фиксированная цена</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Услуги и удобства</label>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mt-2">
|
||||
<label v-for="amenity in availableAmenities" :key="amenity.value"
|
||||
class="flex items-center space-x-2 cursor-pointer">
|
||||
<input type="checkbox" :value="amenity.value" v-model="form.amenities"
|
||||
class="rounded border-gray-300">
|
||||
<span class="text-sm">{{ amenity.label }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Контактная информация -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="text-xl font-semibold">Контактная информация</h2>
|
||||
</div>
|
||||
<div class="card-body space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Email для связи *</label>
|
||||
<input v-model="form.contactEmail" type="email" class="form-input"
|
||||
placeholder="contact@example.com" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Телефон для связи *</label>
|
||||
<input v-model="form.contactPhone" type="tel" class="form-input" placeholder="+7 999 123-45-67"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Веб-сайт (необязательно)</label>
|
||||
<input v-model="form.website" type="url" class="form-input" placeholder="https://example.com">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Кнопки действий -->
|
||||
<div class="flex justify-end gap-4 pt-6">
|
||||
<button type="button" @click="$emit('cancel')" class="btn btn-outline" :disabled="props.loading">
|
||||
Отмена
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" :disabled="props.loading">
|
||||
<span v-if="props.loading">Сохранение...</span>
|
||||
<span v-else>{{ props.object ? 'Обновить' : 'Создать' }} объект</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ObjectItem } from '~/composables/useObjects'
|
||||
|
||||
interface Props {
|
||||
object?: ObjectItem | null
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'submit', data: any): void
|
||||
(e: 'cancel'): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
// Доступные услуги
|
||||
const availableAmenities = [
|
||||
{ value: 'wifi', label: 'Wi-Fi' },
|
||||
{ value: 'parking', label: 'Парковка' },
|
||||
{ value: 'breakfast', label: 'Завтрак' },
|
||||
{ value: 'pool', label: 'Бассейн' },
|
||||
{ value: 'spa', label: 'СПА' },
|
||||
{ value: 'gym', label: 'Тренажерный зал' },
|
||||
{ value: 'air_conditioning', label: 'Кондиционер' },
|
||||
{ value: 'heating', label: 'Отопление' },
|
||||
{ value: 'kitchen', label: 'Кухня' },
|
||||
{ value: 'washing_machine', label: 'Стиральная машина' },
|
||||
{ value: 'tv', label: 'Телевизор' },
|
||||
{ value: 'elevator', label: 'Лифт' }
|
||||
]
|
||||
|
||||
// Форма данных
|
||||
const form = ref({
|
||||
title: '',
|
||||
description: '',
|
||||
type: '',
|
||||
category: '',
|
||||
address: '',
|
||||
city: '',
|
||||
country: '',
|
||||
price: 0,
|
||||
priceUnit: '',
|
||||
amenities: [] as string[],
|
||||
contactEmail: '',
|
||||
contactPhone: '',
|
||||
website: ''
|
||||
})
|
||||
|
||||
// Заполняем форму данными при редактировании
|
||||
watch(() => props.object, (object) => {
|
||||
if (object) {
|
||||
form.value = {
|
||||
title: object.title,
|
||||
description: object.description,
|
||||
type: object.type,
|
||||
category: object.category,
|
||||
address: object.address,
|
||||
city: object.city,
|
||||
country: object.country,
|
||||
price: object.price,
|
||||
priceUnit: object.priceUnit,
|
||||
amenities: object.amenities || [],
|
||||
contactEmail: object.contactEmail,
|
||||
contactPhone: object.contactPhone,
|
||||
website: object.website || ''
|
||||
}
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
const handleSubmit = () => {
|
||||
// Валидация
|
||||
if (!form.value.title || !form.value.type || !form.value.description) {
|
||||
alert('Пожалуйста, заполните все обязательные поля')
|
||||
return
|
||||
}
|
||||
|
||||
emit('submit', form.value)
|
||||
}
|
||||
</script>
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- pages/objects/[id]/edit.vue -->
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50 py-8">
|
||||
<div class="container max-w-4xl">
|
||||
@@ -40,44 +41,79 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// definePageMeta({
|
||||
// middleware: 'auth'
|
||||
// })
|
||||
interface ObjectData {
|
||||
id: number
|
||||
title: string
|
||||
type: string
|
||||
description: string
|
||||
city: string
|
||||
address: string
|
||||
price: number
|
||||
images: string[]
|
||||
amenities: string[]
|
||||
contact: {
|
||||
phone: string
|
||||
email: string
|
||||
}
|
||||
}
|
||||
|
||||
// const route = useRoute()
|
||||
// const { fetchObjectById, updateObject } = useObjects()
|
||||
// const object = ref(null)
|
||||
// const loading = ref(true)
|
||||
// const updating = ref(false)
|
||||
const route = useRoute()
|
||||
const object = ref<ObjectData | null>(null)
|
||||
const loading = ref(true)
|
||||
const updating = ref(false)
|
||||
|
||||
// onMounted(async () => {
|
||||
// const objectId = parseInt(route.params.id as string)
|
||||
// if (objectId) {
|
||||
// const result = await fetchObjectById(objectId)
|
||||
// object.value = result
|
||||
// }
|
||||
// loading.value = false
|
||||
// })
|
||||
// Мок-данные объекта
|
||||
const mockObject: ObjectData = {
|
||||
id: 1,
|
||||
title: 'Гостевой дом "У озера"',
|
||||
type: 'guest_house',
|
||||
description: 'Уютный гостевой дом на берегу живописного озера в Карелии',
|
||||
city: 'Карелия',
|
||||
address: 'ул. Озерная, 15',
|
||||
price: 2800,
|
||||
images: [
|
||||
'/images/objects/lake-house-1.jpg',
|
||||
'/images/objects/lake-house-2.jpg'
|
||||
],
|
||||
amenities: ['Wi-Fi', 'Парковка', 'Завтрак', 'Баня'],
|
||||
contact: {
|
||||
phone: '+7 (911) 123-45-67',
|
||||
email: 'lakehouse@example.com'
|
||||
}
|
||||
}
|
||||
|
||||
// const handleSubmit = async (formData: any) => {
|
||||
// updating.value = true
|
||||
onMounted(async () => {
|
||||
// Имитация загрузки данных
|
||||
await new Promise(resolve => setTimeout(resolve, 800))
|
||||
object.value = mockObject
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
// try {
|
||||
// const objectId = parseInt(route.params.id as string)
|
||||
// await updateObject(objectId, formData)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleSubmit = async (formData: any) => {
|
||||
updating.value = true
|
||||
|
||||
// alert('Объект успешно обновлен!')
|
||||
// await navigateTo(`/objects/${objectId}`)
|
||||
// } catch (error) {
|
||||
// console.error('Error updating object:', error)
|
||||
// alert('Ошибка при обновлении объекта')
|
||||
// } finally {
|
||||
// updating.value = false
|
||||
// }
|
||||
// }
|
||||
try {
|
||||
// Имитация обновления
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
// const handleCancel = () => {
|
||||
// const objectId = route.params.id
|
||||
// navigateTo(`/objects/${objectId}`)
|
||||
// }
|
||||
console.log('Обновление объекта:', {
|
||||
id: parseInt(route.params.id as string),
|
||||
...formData
|
||||
})
|
||||
|
||||
alert('Объект успешно обновлен!')
|
||||
await navigateTo(`/objects/${route.params.id}`)
|
||||
} catch (error) {
|
||||
console.error('Error updating object:', error)
|
||||
alert('Ошибка при обновлении объекта')
|
||||
} finally {
|
||||
updating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
const objectId = route.params.id
|
||||
navigateTo(`/objects/${objectId}`)
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
||||
<!-- pages/objects/create.vue -->
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50 py-8">
|
||||
<div class="container max-w-4xl">
|
||||
@@ -6,47 +7,42 @@
|
||||
<p class="text-gray-600 mt-2">Заполните информацию о вашем объекте</p>
|
||||
</div>
|
||||
|
||||
<ObjectForm
|
||||
:loading="loading"
|
||||
@submit="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
/>
|
||||
<ObjectForm :loading="loading" @submit="handleSubmit" @cancel="handleCancel" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// definePageMeta({
|
||||
// middleware: 'auth'
|
||||
// })
|
||||
const loading = ref(false)
|
||||
|
||||
// const { createObject } = useObjects()
|
||||
// const loading = ref(false)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleSubmit = async (formData: any) => {
|
||||
loading.value = true
|
||||
|
||||
// const handleSubmit = async (formData: any) => {
|
||||
// loading.value = true
|
||||
try {
|
||||
// Имитация создания объекта
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
// try {
|
||||
// await createObject({
|
||||
// ...formData,
|
||||
// userId: 1, // В реальном приложении из авторизации
|
||||
// isActive: true,
|
||||
// images: formData.images || ['/images/placeholder.jpg'],
|
||||
// amenities: formData.amenities || []
|
||||
// })
|
||||
console.log('Создание объекта:', {
|
||||
...formData,
|
||||
userId: 1,
|
||||
isActive: true,
|
||||
images: formData.images || ['/images/placeholder.jpg'],
|
||||
amenities: formData.amenities || []
|
||||
})
|
||||
|
||||
// // Показываем уведомление об успехе
|
||||
// alert('Объект успешно создан!')
|
||||
// await navigateTo('/objects/my-objects')
|
||||
// } catch (error) {
|
||||
// console.error('Error creating object:', error)
|
||||
// alert('Ошибка при создании объекта')
|
||||
// } finally {
|
||||
// loading.value = false
|
||||
// }
|
||||
// }
|
||||
// Показываем уведомление об успехе
|
||||
alert('Объект успешно создан!')
|
||||
await navigateTo('/objects/my-objects')
|
||||
} catch (error) {
|
||||
console.error('Error creating object:', error)
|
||||
alert('Ошибка при создании объекта')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// const handleCancel = () => {
|
||||
// navigateTo('/objects/my-objects')
|
||||
// }
|
||||
const handleCancel = () => {
|
||||
navigateTo('/objects/my-objects')
|
||||
}
|
||||
</script>
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- pages/objects/index.vue -->
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50 py-8">
|
||||
<div class="container">
|
||||
@@ -12,12 +13,42 @@
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- Простой поиск -->
|
||||
<!-- Фильтры поиска -->
|
||||
<div class="search-filters mb-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Тип объекта</label>
|
||||
<select v-model="filters.type" class="form-select">
|
||||
<option value="">Все типы</option>
|
||||
<option value="hotel">Гостиница</option>
|
||||
<option value="sanatorium">Санаторий</option>
|
||||
<option value="tour">Тур</option>
|
||||
<option value="restaurant">Ресторан</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Город</label>
|
||||
<input
|
||||
v-model="filters.city"
|
||||
type="text"
|
||||
class="form-input"
|
||||
placeholder="Введите город"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Цена до</label>
|
||||
<input
|
||||
v-model="filters.maxPrice"
|
||||
type="number"
|
||||
class="form-input"
|
||||
placeholder="Макс. цена"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-4">
|
||||
<div class="form-group flex-1">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
v-model="filters.search"
|
||||
type="text"
|
||||
class="form-input"
|
||||
placeholder="Поиск по названию, городу или описанию..."
|
||||
@@ -71,31 +102,107 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ObjectItem } from '~/composables/useObjects'
|
||||
|
||||
const route = useRoute()
|
||||
const { objects, loading, fetchObjects } = useObjects()
|
||||
|
||||
const searchQuery = ref(route.query.search as string || '')
|
||||
|
||||
// Загрузка объектов при монтировании
|
||||
onMounted(() => {
|
||||
if (searchQuery.value) {
|
||||
searchObjects()
|
||||
} else {
|
||||
fetchObjects()
|
||||
interface ObjectItem {
|
||||
id: number
|
||||
title: string
|
||||
type: string
|
||||
city: string
|
||||
price: number
|
||||
rating: number
|
||||
image: string
|
||||
description: string
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
// Мок-данные
|
||||
const mockObjects: ObjectItem[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Горный отель "Эдельвейс"',
|
||||
type: 'hotel',
|
||||
city: 'Сочи',
|
||||
price: 4500,
|
||||
rating: 4.8,
|
||||
image: '/images/hotels/edelweiss.jpg',
|
||||
description: 'Комфортабельный отель в горах с видом на море',
|
||||
isActive: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Тур по Алтаю',
|
||||
type: 'tour',
|
||||
city: 'Горно-Алтайск',
|
||||
price: 12500,
|
||||
rating: 4.9,
|
||||
image: '/images/tours/altai.jpg',
|
||||
description: '7-дневный тур по самым живописным местам Алтая',
|
||||
isActive: true
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Санаторий "Здоровье"',
|
||||
type: 'sanatorium',
|
||||
city: 'Кисловодск',
|
||||
price: 3200,
|
||||
rating: 4.6,
|
||||
image: '/images/sanatoriums/health.jpg',
|
||||
description: 'Лечебно-оздоровительный комплекс с минеральными водами',
|
||||
isActive: true
|
||||
}
|
||||
]
|
||||
|
||||
const objects = ref<ObjectItem[]>(mockObjects)
|
||||
const loading = ref(false)
|
||||
|
||||
const filters = ref({
|
||||
search: '',
|
||||
type: '',
|
||||
city: '',
|
||||
maxPrice: null as number | null
|
||||
})
|
||||
|
||||
const searchObjects = async () => {
|
||||
await fetchObjects({
|
||||
search: searchQuery.value
|
||||
})
|
||||
loading.value = true
|
||||
// Имитация загрузки
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
let filtered = [...mockObjects]
|
||||
|
||||
if (filters.value.search) {
|
||||
const search = filters.value.search.toLowerCase()
|
||||
filtered = filtered.filter(obj =>
|
||||
obj.title.toLowerCase().includes(search) ||
|
||||
obj.city.toLowerCase().includes(search) ||
|
||||
obj.description.toLowerCase().includes(search)
|
||||
)
|
||||
}
|
||||
|
||||
if (filters.value.type) {
|
||||
filtered = filtered.filter(obj => obj.type === filters.value.type)
|
||||
}
|
||||
|
||||
if (filters.value.city) {
|
||||
filtered = filtered.filter(obj =>
|
||||
obj.city.toLowerCase().includes(filters.value.city.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
if (filters.value.maxPrice) {
|
||||
filtered = filtered.filter(obj => obj.price <= filters.value.maxPrice!)
|
||||
}
|
||||
|
||||
objects.value = filtered
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const resetSearch = async () => {
|
||||
searchQuery.value = ''
|
||||
await fetchObjects()
|
||||
filters.value = {
|
||||
search: '',
|
||||
type: '',
|
||||
city: '',
|
||||
maxPrice: null
|
||||
}
|
||||
objects.value = mockObjects
|
||||
}
|
||||
|
||||
const navigateToObject = (id: number) => {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!-- pages/objects/my-objects.vue -->
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50 py-8">
|
||||
<div class="container">
|
||||
@@ -17,12 +18,47 @@
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Информация -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
||||
<p class="text-blue-700">
|
||||
💡 Это демо-режим. В реальном приложении здесь будут отображаться только ваши объекты.
|
||||
</p>
|
||||
<!-- Фильтры -->
|
||||
<div class="card mb-6">
|
||||
<div class="card-body">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Статус</label>
|
||||
<select v-model="filters.status" class="form-select">
|
||||
<option value="">Все статусы</option>
|
||||
<option value="active">Активные</option>
|
||||
<option value="inactive">Неактивные</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Тип</label>
|
||||
<select v-model="filters.type" class="form-select">
|
||||
<option value="">Все типы</option>
|
||||
<option value="hotel">Гостиница</option>
|
||||
<option value="sanatorium">Санаторий</option>
|
||||
<option value="tour">Тур</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Поиск</label>
|
||||
<input
|
||||
v-model="filters.search"
|
||||
type="text"
|
||||
class="form-input"
|
||||
placeholder="Название или город"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 mt-4">
|
||||
<button class="btn btn-primary" @click="applyFilters">
|
||||
Применить
|
||||
</button>
|
||||
<button class="btn btn-outline" @click="resetFilters">
|
||||
Сбросить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -126,15 +162,116 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// import { ObjectItem } from '~/composables/useObjects'
|
||||
interface MyObjectItem {
|
||||
id: number
|
||||
title: string
|
||||
type: string
|
||||
city: string
|
||||
price: number
|
||||
isActive: boolean
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
// // Убрали middleware - страница доступна всем
|
||||
// Мок-данные
|
||||
const mockMyObjects: MyObjectItem[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Гостевой дом "У озера"',
|
||||
type: 'guest_house',
|
||||
city: 'Карелия',
|
||||
price: 2800,
|
||||
isActive: true,
|
||||
createdAt: '2024-01-15'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Экскурсия по историческому центру',
|
||||
type: 'excursion',
|
||||
city: 'Санкт-Петербург',
|
||||
price: 1500,
|
||||
isActive: true,
|
||||
createdAt: '2024-01-10'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Горнолыжный курорт "Снежный"',
|
||||
type: 'hotel',
|
||||
city: 'Красная Поляна',
|
||||
price: 5200,
|
||||
isActive: false,
|
||||
createdAt: '2024-01-05'
|
||||
}
|
||||
]
|
||||
|
||||
// const { fetchMyObjects, deleteObject, objects: myObjects, loading } = useObjects()
|
||||
const myObjects = ref<MyObjectItem[]>(mockMyObjects)
|
||||
const loading = ref(false)
|
||||
|
||||
// onMounted(async () => {
|
||||
// await fetchMyObjects()
|
||||
// })
|
||||
const filters = ref({
|
||||
status: '',
|
||||
type: '',
|
||||
search: ''
|
||||
})
|
||||
|
||||
// ... остальные функции без изменений ...
|
||||
const applyFilters = () => {
|
||||
let filtered = [...mockMyObjects]
|
||||
|
||||
if (filters.value.status) {
|
||||
filtered = filtered.filter(obj =>
|
||||
filters.value.status === 'active' ? obj.isActive : !obj.isActive
|
||||
)
|
||||
}
|
||||
|
||||
if (filters.value.type) {
|
||||
filtered = filtered.filter(obj => obj.type === filters.value.type)
|
||||
}
|
||||
|
||||
if (filters.value.search) {
|
||||
const search = filters.value.search.toLowerCase()
|
||||
filtered = filtered.filter(obj =>
|
||||
obj.title.toLowerCase().includes(search) ||
|
||||
obj.city.toLowerCase().includes(search)
|
||||
)
|
||||
}
|
||||
|
||||
myObjects.value = filtered
|
||||
}
|
||||
|
||||
const resetFilters = () => {
|
||||
filters.value = {
|
||||
status: '',
|
||||
type: '',
|
||||
search: ''
|
||||
}
|
||||
myObjects.value = mockMyObjects
|
||||
}
|
||||
|
||||
const deleteObject = async (id: number) => {
|
||||
if (confirm('Вы уверены, что хотите удалить этот объект?')) {
|
||||
// Имитация удаления
|
||||
myObjects.value = myObjects.value.filter(obj => obj.id !== id)
|
||||
}
|
||||
}
|
||||
|
||||
const getTypeLabel = (type: string) => {
|
||||
const types: Record<string, string> = {
|
||||
hotel: 'Гостиница',
|
||||
sanatorium: 'Санаторий',
|
||||
guest_house: 'Гостевой дом',
|
||||
tour: 'Тур',
|
||||
excursion: 'Экскурсия'
|
||||
}
|
||||
return types[type] || type
|
||||
}
|
||||
|
||||
const formatPrice = (price: number) => {
|
||||
return new Intl.NumberFormat('ru-RU', {
|
||||
style: 'currency',
|
||||
currency: 'RUB',
|
||||
minimumFractionDigits: 0
|
||||
}).format(price)
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString('ru-RU')
|
||||
}
|
||||
</script>
|
||||
@@ -1,6 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Страница</h1>
|
||||
<p>Содержимое страницы</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -298,6 +298,7 @@ const verifyEmailCode = async () => {
|
||||
verificationError.value = 'Неверный код подтверждения';
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("debug. Error: " + error)
|
||||
verificationError.value = 'Ошибка при проверке кода. Попробуйте позже.';
|
||||
} finally {
|
||||
isVerifyingCode.value = false;
|
||||
|
||||
Reference in New Issue
Block a user