Files
tp/main_dc/yalarba/yalarba-nuxt/app/pages/objects/[id].vue
T
2026-06-12 00:29:34 +05:00

211 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="object-detail">
<div v-if="loading" class="container section">
<div class="skeleton" style="height: 400px; margin-bottom: 24px" />
<div class="skeleton" style="height: 32px; width: 60%; margin-bottom: 16px" />
<div class="skeleton" style="height: 16px; width: 40%; margin-bottom: 8px" />
<div class="skeleton" style="height: 100px" />
</div>
<template v-else-if="object">
<section class="object-gallery">
<div class="container">
<div class="object-gallery__main">
<img
v-if="object.images?.[0]"
:src="object.images[0]"
:alt="object.title"
/>
<div v-else class="object-gallery__placeholder">
<svg width="64" height="64" viewBox="0 0 24 24" fill="none">
<rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2"/>
<circle cx="8.5" cy="8.5" r="1.5" stroke="currentColor" stroke-width="2"/>
<path d="M21 15l-5-5L5 21" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
</div>
</div>
</section>
<section class="section">
<div class="container">
<div class="object-layout">
<div class="object-content">
<div class="object-header">
<h1>{{ object.title }}</h1>
<div class="object-header__meta">
<span v-if="object.category_name" class="tag">{{ object.category_name }}</span>
<span v-if="object.rating" class="object-rating">
<svg width="20" height="20" viewBox="0 0 24 24" fill="#FF8833">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
</svg>
{{ object.rating.toFixed(1) }}
<span v-if="object.review_count">({{ object.review_count }})</span>
</span>
</div>
</div>
<div v-if="object.location" class="object-location">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z" stroke="currentColor" stroke-width="2"/>
<circle cx="12" cy="10" r="3" stroke="currentColor" stroke-width="2"/>
</svg>
{{ object.location }}
</div>
<div class="object-description">
<h3>Описание</h3>
<p class="body-text">{{ object.description }}</p>
</div>
<div class="object-reviews">
<h3>Отзывы</h3>
<p class="small-text">Здесь будут отзывы о месте</p>
</div>
</div>
<aside class="object-sidebar">
<div class="object-actions">
<button class="btn btn--secondary btn--full btn--md">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" stroke="currentColor" stroke-width="2" fill="none"/>
</svg>
В избранное
</button>
</div>
</aside>
</div>
</div>
</section>
</template>
<div v-else-if="error" class="container section">
<p class="body-text--gray">Объект не найден</p>
</div>
</div>
</template>
<script setup lang="ts">
definePageMeta({
title: 'Объект',
})
const route = useRoute()
const objectsStore = useObjectsStore()
const { currentObject: object, loading, error } = storeToRefs(objectsStore)
const id = computed(() => Number(route.params.id))
onMounted(() => {
if (id.value) {
objectsStore.fetchObject(id.value)
}
})
watch(id, (newId) => {
if (newId) {
objectsStore.fetchObject(newId)
}
})
</script>
<style scoped>
.object-gallery {
background: var(--color-bg-gray);
}
.object-gallery__main {
max-height: 400px;
overflow: hidden;
}
.object-gallery__main img {
width: 100%;
height: 400px;
object-fit: cover;
}
.object-gallery__placeholder {
height: 400px;
display: flex;
align-items: center;
justify-content: center;
color: var(--color-text-gray);
}
.object-layout {
display: grid;
grid-template-columns: 1fr 280px;
gap: 40px;
align-items: start;
}
.object-header {
margin-bottom: 16px;
}
.object-header h1 {
margin-bottom: 12px;
}
.object-header__meta {
display: flex;
align-items: center;
gap: 12px;
}
.object-rating {
display: flex;
align-items: center;
gap: 4px;
font-family: var(--font-body);
font-size: var(--font-size-body);
font-weight: var(--font-weight-semibold);
color: var(--color-text-black);
}
.object-location {
display: flex;
align-items: center;
gap: 8px;
font-family: var(--font-body);
font-size: var(--font-size-body);
color: var(--color-text-gray);
margin-bottom: 24px;
}
.object-description {
margin-bottom: 40px;
}
.object-description h3 {
margin-bottom: 16px;
}
.object-reviews h3 {
margin-bottom: 16px;
}
.object-sidebar {
position: sticky;
top: calc(var(--header-height) + 24px);
}
.object-actions {
background: var(--color-white);
border: 1px solid var(--color-stroke);
border-radius: var(--radius-md);
padding: 20px;
}
@media (max-width: 744px) {
.object-layout {
grid-template-columns: 1fr;
}
.object-sidebar {
position: static;
}
}
</style>