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>
|
<template>
|
||||||
<div class="min-h-screen bg-gray-50 py-8">
|
<div class="min-h-screen bg-gray-50 py-8">
|
||||||
<div class="container max-w-4xl">
|
<div class="container max-w-4xl">
|
||||||
@@ -40,44 +41,79 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// definePageMeta({
|
interface ObjectData {
|
||||||
// middleware: 'auth'
|
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 route = useRoute()
|
||||||
// const { fetchObjectById, updateObject } = useObjects()
|
const object = ref<ObjectData | null>(null)
|
||||||
// const object = ref(null)
|
const loading = ref(true)
|
||||||
// const loading = ref(true)
|
const updating = ref(false)
|
||||||
// const updating = ref(false)
|
|
||||||
|
|
||||||
// onMounted(async () => {
|
// Мок-данные объекта
|
||||||
// const objectId = parseInt(route.params.id as string)
|
const mockObject: ObjectData = {
|
||||||
// if (objectId) {
|
id: 1,
|
||||||
// const result = await fetchObjectById(objectId)
|
title: 'Гостевой дом "У озера"',
|
||||||
// object.value = result
|
type: 'guest_house',
|
||||||
// }
|
description: 'Уютный гостевой дом на берегу живописного озера в Карелии',
|
||||||
// loading.value = false
|
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) => {
|
onMounted(async () => {
|
||||||
// updating.value = true
|
// Имитация загрузки данных
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800))
|
||||||
|
object.value = mockObject
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const handleSubmit = async (formData: any) => {
|
||||||
|
updating.value = true
|
||||||
|
|
||||||
// try {
|
try {
|
||||||
// const objectId = parseInt(route.params.id as string)
|
// Имитация обновления
|
||||||
// await updateObject(objectId, formData)
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
|
||||||
// alert('Объект успешно обновлен!')
|
console.log('Обновление объекта:', {
|
||||||
// await navigateTo(`/objects/${objectId}`)
|
id: parseInt(route.params.id as string),
|
||||||
// } catch (error) {
|
...formData
|
||||||
// console.error('Error updating object:', error)
|
})
|
||||||
// alert('Ошибка при обновлении объекта')
|
|
||||||
// } finally {
|
alert('Объект успешно обновлен!')
|
||||||
// updating.value = false
|
await navigateTo(`/objects/${route.params.id}`)
|
||||||
// }
|
} catch (error) {
|
||||||
// }
|
console.error('Error updating object:', error)
|
||||||
|
alert('Ошибка при обновлении объекта')
|
||||||
|
} finally {
|
||||||
|
updating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
// const objectId = route.params.id
|
const objectId = route.params.id
|
||||||
// navigateTo(`/objects/${objectId}`)
|
navigateTo(`/objects/${objectId}`)
|
||||||
// }
|
}
|
||||||
</script>
|
</script>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- pages/objects/create.vue -->
|
||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen bg-gray-50 py-8">
|
<div class="min-h-screen bg-gray-50 py-8">
|
||||||
<div class="container max-w-4xl">
|
<div class="container max-w-4xl">
|
||||||
@@ -6,47 +7,42 @@
|
|||||||
<p class="text-gray-600 mt-2">Заполните информацию о вашем объекте</p>
|
<p class="text-gray-600 mt-2">Заполните информацию о вашем объекте</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ObjectForm
|
<ObjectForm :loading="loading" @submit="handleSubmit" @cancel="handleCancel" />
|
||||||
:loading="loading"
|
|
||||||
@submit="handleSubmit"
|
|
||||||
@cancel="handleCancel"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// definePageMeta({
|
const loading = ref(false)
|
||||||
// middleware: 'auth'
|
|
||||||
// })
|
|
||||||
|
|
||||||
// const { createObject } = useObjects()
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
// const loading = ref(false)
|
const handleSubmit = async (formData: any) => {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
// const handleSubmit = async (formData: any) => {
|
try {
|
||||||
// loading.value = true
|
// Имитация создания объекта
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
// try {
|
|
||||||
// await createObject({
|
|
||||||
// ...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
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const handleCancel = () => {
|
console.log('Создание объекта:', {
|
||||||
// navigateTo('/objects/my-objects')
|
...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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
navigateTo('/objects/my-objects')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- pages/objects/index.vue -->
|
||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen bg-gray-50 py-8">
|
<div class="min-h-screen bg-gray-50 py-8">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -12,12 +13,42 @@
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Простой поиск -->
|
<!-- Фильтры поиска -->
|
||||||
<div class="search-filters mb-8">
|
<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="flex gap-4">
|
||||||
<div class="form-group flex-1">
|
<div class="form-group flex-1">
|
||||||
<input
|
<input
|
||||||
v-model="searchQuery"
|
v-model="filters.search"
|
||||||
type="text"
|
type="text"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
placeholder="Поиск по названию, городу или описанию..."
|
placeholder="Поиск по названию, городу или описанию..."
|
||||||
@@ -71,31 +102,107 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ObjectItem } from '~/composables/useObjects'
|
interface ObjectItem {
|
||||||
|
id: number
|
||||||
|
title: string
|
||||||
|
type: string
|
||||||
|
city: string
|
||||||
|
price: number
|
||||||
|
rating: number
|
||||||
|
image: string
|
||||||
|
description: string
|
||||||
|
isActive: boolean
|
||||||
|
}
|
||||||
|
|
||||||
const route = useRoute()
|
// Мок-данные
|
||||||
const { objects, loading, fetchObjects } = useObjects()
|
const mockObjects: ObjectItem[] = [
|
||||||
|
{
|
||||||
const searchQuery = ref(route.query.search as string || '')
|
id: 1,
|
||||||
|
title: 'Горный отель "Эдельвейс"',
|
||||||
// Загрузка объектов при монтировании
|
type: 'hotel',
|
||||||
onMounted(() => {
|
city: 'Сочи',
|
||||||
if (searchQuery.value) {
|
price: 4500,
|
||||||
searchObjects()
|
rating: 4.8,
|
||||||
} else {
|
image: '/images/hotels/edelweiss.jpg',
|
||||||
fetchObjects()
|
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 () => {
|
const searchObjects = async () => {
|
||||||
await fetchObjects({
|
loading.value = true
|
||||||
search: searchQuery.value
|
// Имитация загрузки
|
||||||
})
|
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 () => {
|
const resetSearch = async () => {
|
||||||
searchQuery.value = ''
|
filters.value = {
|
||||||
await fetchObjects()
|
search: '',
|
||||||
|
type: '',
|
||||||
|
city: '',
|
||||||
|
maxPrice: null
|
||||||
|
}
|
||||||
|
objects.value = mockObjects
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigateToObject = (id: number) => {
|
const navigateToObject = (id: number) => {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- pages/objects/my-objects.vue -->
|
||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen bg-gray-50 py-8">
|
<div class="min-h-screen bg-gray-50 py-8">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -17,12 +18,47 @@
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Информация -->
|
|
||||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
<!-- Фильтры -->
|
||||||
<p class="text-blue-700">
|
<div class="card mb-6">
|
||||||
💡 Это демо-режим. В реальном приложении здесь будут отображаться только ваши объекты.
|
<div class="card-body">
|
||||||
</p>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -126,15 +162,116 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 () => {
|
const filters = ref({
|
||||||
// await fetchMyObjects()
|
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>
|
</script>
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h1>Страница</h1>
|
|
||||||
<p>Содержимое страницы</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -298,6 +298,7 @@ const verifyEmailCode = async () => {
|
|||||||
verificationError.value = 'Неверный код подтверждения';
|
verificationError.value = 'Неверный код подтверждения';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log("debug. Error: " + error)
|
||||||
verificationError.value = 'Ошибка при проверке кода. Попробуйте позже.';
|
verificationError.value = 'Ошибка при проверке кода. Попробуйте позже.';
|
||||||
} finally {
|
} finally {
|
||||||
isVerifyingCode.value = false;
|
isVerifyingCode.value = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user