modified: main_dc/yalarba/easySite/easySite/app/components/layout/Header.vue

modified:   main_dc/yalarba/easySite/easySite/app/composables/useObjects.ts
	modified:   main_dc/yalarba/easySite/easySite/app/pages/auth/login.vue
	modified:   main_dc/yalarba/easySite/easySite/app/pages/auth/register.vue
	modified:   main_dc/yalarba/easySite/easySite/app/pages/index.vue
	modified:   main_dc/yalarba/easySite/easySite/app/pages/objects/[id]/edit.vue
	modified:   main_dc/yalarba/easySite/easySite/app/pages/objects/create.vue
	modified:   main_dc/yalarba/easySite/easySite/app/pages/objects/my-objects.vue
	modified:   main_dc/yalarba/easySite/easySite/app/pages/profile/index.vue
	modified:   main_dc/yalarba/easySite/easySite/nuxt.config.ts
set main index page for easysite102.ru with header and search bar
This commit is contained in:
2025-10-29 02:26:06 +05:00
parent b3d7de5857
commit e06ac476e6
10 changed files with 976 additions and 540 deletions
@@ -1,64 +1,162 @@
<template>
<header class="bg-white shadow-sm sticky top-0 z-50">
<header class="header">
<div class="container">
<div class="flex justify-between items-center py-4">
<div class="header-content">
<!-- Логотип -->
<NuxtLink to="/" class="flex items-center gap-3">
<div class="w-10 h-10 bg-primary-500 rounded-lg flex items-center justify-center">
<span class="text-white font-bold text-lg">T</span>
<NuxtLink to="/" class="logo">
<div class="logo-icon">
<span class="logo-text">T</span>
</div>
<span class="font-bold text-2xl">TravelEasy</span>
<span class="logo-name">TravelEasy</span>
</NuxtLink>
<!-- Навигация -->
<nav class="hidden md:flex gap-6">
<NuxtLink
to="/"
class="nav-link"
:class="{ active: $route.path === '/' }"
>
Главная
<nav class="nav">
<NuxtLink to="/profile" class="nav-link">
👤 Профиль
</NuxtLink>
<NuxtLink
to="/objects"
class="nav-link"
:class="{ active: $route.path.startsWith('/objects') }"
>
Объекты
<NuxtLink to="/auth/login" class="nav-link">
🔑 Войти
</NuxtLink>
<NuxtLink
to="/objects/my-objects"
class="nav-link"
>
Мои объекты
</NuxtLink>
<NuxtLink
to="/objects/create"
class="nav-link"
>
Добавить объект
<NuxtLink to="/auth/register" class="btn btn-primary nav-button">
📝 Регистрация
</NuxtLink>
</nav>
<!-- Действия пользователя -->
<div class="flex items-center gap-4">
<NuxtLink to="/objects/create" class="btn btn-primary hidden sm:block">
Добавить объект
</NuxtLink>
<div class="flex gap-2">
<NuxtLink to="/auth/login" class="btn btn-outline">
Войти
</NuxtLink>
<NuxtLink to="/auth/register" class="btn btn-primary">
Регистрация
</NuxtLink>
</div>
</div>
</div>
</div>
</header>
</template>
<script setup lang="ts">
// Убрана проверка авторизации - все страницы доступны
// Простой хедер без проверки авторизации
</script>
<style scoped>
.header {
background: var(--bg-primary);
box-shadow: var(--shadow-sm);
position: sticky;
top: 0;
z-index: 50;
border-bottom: 1px solid var(--border-light);
width: 100%;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 0;
width: 100%;
}
.logo {
display: flex;
align-items: center;
gap: 0.75rem;
text-decoration: none;
color: var(--text-primary);
}
.logo:hover {
color: var(--text-primary);
}
.logo-icon {
width: 2.5rem;
height: 2.5rem;
background: var(--primary-500);
border-radius: var(--radius-lg);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.logo-text {
color: var(--text-inverse);
font-weight: bold;
font-size: 1.125rem;
}
.logo-name {
font-weight: bold;
font-size: 1.5rem;
white-space: nowrap;
}
.nav {
display: flex;
align-items: center;
gap: 1rem;
flex-shrink: 0;
}
.nav-link {
color: var(--text-secondary);
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: var(--radius-md);
transition: all 0.2s;
font-weight: 500;
white-space: nowrap;
}
.nav-link:hover {
color: var(--primary-600);
background: var(--primary-50);
}
.nav-button {
white-space: nowrap;
flex-shrink: 0;
}
@media (max-width: 768px) {
.header-content {
padding: 0.75rem 0;
}
.nav {
gap: 0.5rem;
}
.logo-name {
font-size: 1.25rem;
}
.nav-link {
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
}
.logo-icon {
width: 2rem;
height: 2rem;
}
.logo-text {
font-size: 1rem;
}
}
@media (max-width: 480px) {
.logo-name {
display: none;
}
.nav-link {
padding: 0.5rem;
font-size: 0.8rem;
}
.nav-link span {
display: none;
}
.nav-link::before {
content: attr(data-icon);
font-size: 1.1rem;
}
}
</style>
@@ -1,89 +1,5 @@
import { ref } from 'vue'
// В файле composables/useObjects.ts обновляем fetchObjects:
export interface ObjectItem {
id: number
title: string
description: string
type: 'hotel' | 'apartment' | 'villa' | 'camping' | 'restaurant' | 'attraction'
category: string
address: string
city: string
country: string
price: number
priceUnit: 'per_night' | 'per_person' | 'fixed'
amenities: string[]
images: string[]
contactEmail: string
contactPhone: string
website?: string
coordinates?: {
lat: number
lng: number
}
isActive: boolean
createdAt: string
updatedAt: string
userId: number
rating?: number
reviewCount?: number
}
export const useObjects = () => {
const objects = ref<ObjectItem[]>([])
const loading = ref(false)
const error = ref<string | null>(null)
// Мок-данные объектов
const mockObjects: ObjectItem[] = [
{
id: 1,
title: 'Отель "Морской бриз"',
description: 'Комфортабельный отель с видом на море. Идеальное место для отдыха всей семьей.',
type: 'hotel',
category: 'accommodation',
address: 'ул. Приморская, 15',
city: 'Сочи',
country: 'Россия',
price: 5000,
priceUnit: 'per_night',
amenities: ['wifi', 'parking', 'breakfast', 'pool', 'spa'],
images: ['/images/hotel1.jpg', '/images/hotel2.jpg'],
contactEmail: 'hotel@example.com',
contactPhone: '+7 999 123-45-67',
website: 'https://hotel-example.com',
coordinates: { lat: 43.5855, lng: 39.7231 },
isActive: true,
createdAt: '2024-01-15T10:00:00Z',
updatedAt: '2024-01-15T10:00:00Z',
userId: 1,
rating: 4.5,
reviewCount: 23
},
{
id: 2,
title: 'Апартаменты в центре',
description: 'Современные апартаменты в историческом центре города.',
type: 'apartment',
category: 'accommodation',
address: 'ул. Центральная, 25',
city: 'Москва',
country: 'Россия',
price: 3500,
priceUnit: 'per_night',
amenities: ['wifi', 'kitchen', 'washing_machine'],
images: ['/images/apartment1.jpg'],
contactEmail: 'apart@example.com',
contactPhone: '+7 999 765-43-21',
isActive: true,
createdAt: '2024-01-10T14:30:00Z',
updatedAt: '2024-01-12T09:15:00Z',
userId: 2,
rating: 4.2,
reviewCount: 15
}
]
// Получить все объекты
const fetchObjects = async (filters?: any): Promise<ObjectItem[]> => {
loading.value = true
error.value = null
@@ -94,6 +10,14 @@ export const useObjects = () => {
// Применяем фильтры
if (filters) {
if (filters.search) {
const searchTerm = filters.search.toLowerCase()
filteredObjects = filteredObjects.filter(obj =>
obj.title.toLowerCase().includes(searchTerm) ||
obj.city.toLowerCase().includes(searchTerm) ||
obj.description.toLowerCase().includes(searchTerm)
)
}
if (filters.type) {
filteredObjects = filteredObjects.filter(obj => obj.type === filters.type)
}
@@ -113,113 +37,3 @@ export const useObjects = () => {
}, 500)
})
}
// Получить объект по ID
const fetchObjectById = async (id: number): Promise<ObjectItem | null> => {
loading.value = true
return new Promise((resolve) => {
setTimeout(() => {
const object = mockObjects.find(obj => obj.id === id) || null
loading.value = false
resolve(object)
}, 300)
})
}
// Создать новый объект
const createObject = async (objectData: Omit<ObjectItem, 'id' | 'createdAt' | 'updatedAt'>): Promise<ObjectItem> => {
loading.value = true
error.value = null
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
const newObject: ObjectItem = {
...objectData,
id: Date.now(),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}
mockObjects.push(newObject)
objects.value.push(newObject)
loading.value = false
resolve(newObject)
} catch (err) {
error.value = 'Ошибка при создании объекта'
loading.value = false
reject(err)
}
}, 1000)
})
}
// Обновить объект
const updateObject = async (id: number, objectData: Partial<ObjectItem>): Promise<ObjectItem> => {
loading.value = true
error.value = null
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
const index = mockObjects.findIndex(obj => obj.id === id)
if (index === -1) {
throw new Error('Объект не найден')
}
const updatedObject: ObjectItem = {
...mockObjects[index],
...objectData,
updatedAt: new Date().toISOString()
}
mockObjects[index] = updatedObject
objects.value[index] = updatedObject
loading.value = false
resolve(updatedObject)
} catch (err) {
error.value = 'Ошибка при обновлении объекта'
loading.value = false
reject(err)
}
}, 800)
})
}
// Удалить объект
const deleteObject = async (id: number): Promise<void> => {
loading.value = true
return new Promise((resolve) => {
setTimeout(() => {
const index = mockObjects.findIndex(obj => obj.id === id)
if (index !== -1) {
mockObjects.splice(index, 1)
objects.value.splice(index, 1)
}
loading.value = false
resolve()
}, 500)
})
}
// Получить объекты текущего пользователя
const fetchMyObjects = async (): Promise<ObjectItem[]> => {
// В реальном приложении здесь будет userId из авторизации
const currentUserId = 1
return fetchObjects({ userId: currentUserId })
}
return {
objects,
loading,
error,
fetchObjects,
fetchObjectById,
createObject,
updateObject,
deleteObject,
fetchMyObjects
}
}
@@ -1,23 +1,53 @@
<template>
<div class="min-h-screen bg-gray-50 flex items-center justify-center py-12">
<div class="max-w-md w-full">
<div class="auth-page">
<div class="auth-container">
<div class="auth-card">
<div class="card">
<div class="card-header text-center">
<h1 class="text-2xl font-bold">Вход в систему</h1>
<p class="text-gray-600 mt-2">Введите свои учетные данные</p>
<h1 class="auth-title">Вход в систему</h1>
</div>
<div class="card-body">
<LoginForm />
<form @submit.prevent="handleSubmit" class="auth-form">
<div class="form-group">
<label class="form-label">Email</label>
<input
v-model="form.email"
type="email"
class="form-input"
placeholder="your@email.com"
required
>
</div>
<div class="form-group">
<label class="form-label">Пароль</label>
<input
v-model="form.password"
type="password"
class="form-input"
placeholder="Введите пароль"
required
>
</div>
<button type="submit" class="btn btn-primary auth-button">
Войти
</button>
</form>
</div>
<div class="card-footer text-center">
<p class="text-gray-600">
<p class="auth-footer-text">
Нет аккаунта?
<NuxtLink to="/auth/register" class="text-primary-600 font-medium">
<NuxtLink to="/auth/register" class="auth-link">
Зарегистрируйтесь
</NuxtLink>
</p>
<NuxtLink to="/" class="auth-link home-link">
На главную
</NuxtLink>
</div>
</div>
</div>
</div>
@@ -25,7 +55,71 @@
</template>
<script setup lang="ts">
definePageMeta({
layout: 'auth'
const form = ref({
email: '',
password: ''
})
const handleSubmit = () => {
// В демо-режиме просто переходим в профиль
alert('Демо-режим: вход выполнен')
navigateTo('/profile')
}
</script>
<style scoped>
.auth-page {
min-height: 100vh;
background: var(--bg-secondary);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem 1rem;
}
.auth-container {
width: 100%;
max-width: 400px;
}
.auth-card {
width: 100%;
}
.auth-title {
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.auth-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.auth-button {
width: 100%;
margin-top: 0.5rem;
}
.auth-footer-text {
color: var(--text-secondary);
margin-bottom: 1rem;
}
.auth-link {
color: var(--primary-600);
font-weight: 500;
text-decoration: none;
}
.auth-link:hover {
color: var(--primary-700);
}
.home-link {
display: inline-block;
margin-top: 0.5rem;
}
</style>
@@ -1,52 +1,76 @@
<template>
<div class="min-h-screen flex items-center justify-center bg-gray-50">
<div class="max-w-md w-full space-y-8">
<div>
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
Регистрация
</h2>
<div class="auth-page">
<div class="auth-container">
<div class="auth-card">
<div class="card">
<div class="card-header text-center">
<h1 class="auth-title">Регистрация</h1>
</div>
<form class="mt-8 space-y-6" @submit.prevent="handleRegister">
<div>
<div class="card-body">
<form @submit.prevent="handleSubmit" class="auth-form">
<div class="form-group">
<label class="form-label">Имя</label>
<input
v-model="form.name"
type="text"
class="form-input"
placeholder="Ваше имя"
required
class="relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Имя"
/>
>
</div>
<div>
<div class="form-group">
<label class="form-label">Email</label>
<input
v-model="form.email"
type="email"
class="form-input"
placeholder="your@email.com"
required
class="relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Email"
/>
>
</div>
<div>
<div class="form-group">
<label class="form-label">Пароль</label>
<input
v-model="form.password"
type="password"
class="form-input"
placeholder="Придумайте пароль"
required
class="relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Пароль"
/>
</div>
<div>
<button
type="submit"
class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
</div>
<div class="form-group">
<label class="form-label">Подтверждение пароля</label>
<input
v-model="form.passwordConfirm"
type="password"
class="form-input"
placeholder="Повторите пароль"
required
>
</div>
<button type="submit" class="btn btn-primary auth-button">
Зарегистрироваться
</button>
</div>
</form>
<div class="text-center">
<NuxtLink to="/auth/login" class="text-indigo-600 hover:text-indigo-500">
Уже есть аккаунт? Войдите
</div>
<div class="card-footer text-center">
<p class="auth-footer-text">
Уже есть аккаунт?
<NuxtLink to="/auth/login" class="auth-link">
Войдите
</NuxtLink>
</p>
<NuxtLink to="/" class="auth-link home-link">
На главную
</NuxtLink>
</div>
</div>
</div>
</div>
</div>
@@ -56,10 +80,75 @@
const form = ref({
name: '',
email: '',
password: ''
password: '',
passwordConfirm: ''
})
const handleRegister = async () => {
console.log('Register attempt:', form.value)
const handleSubmit = () => {
if (form.value.password !== form.value.passwordConfirm) {
alert('Пароли не совпадают')
return
}
// В демо-режиме просто переходим в профиль
alert('Демо-режим: регистрация выполнена')
navigateTo('/profile')
}
</script>
<style scoped>
.auth-page {
min-height: 100vh;
background: var(--bg-secondary);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem 1rem;
}
.auth-container {
width: 100%;
max-width: 400px;
}
.auth-card {
width: 100%;
}
.auth-title {
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.auth-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.auth-button {
width: 100%;
margin-top: 0.5rem;
}
.auth-footer-text {
color: var(--text-secondary);
margin-bottom: 1rem;
}
.auth-link {
color: var(--primary-600);
font-weight: 500;
text-decoration: none;
}
.auth-link:hover {
color: var(--primary-700);
}
.home-link {
display: inline-block;
margin-top: 0.5rem;
}
</style>
@@ -1,86 +1,41 @@
<template>
<div class="min-h-screen bg-gray-50">
<div class="page-wrapper">
<!-- Хедер -->
<Header />
<!-- Hero Section -->
<section class="hero-section">
<div class="container">
<div class="relative z-10 py-20">
<div class="max-w-2xl mx-auto text-center">
<h1 class="text-5xl font-bold mb-6">База объектов для путешествий</h1>
<p class="text-xl mb-8 opacity-90">Добавляйте и находите лучшие места для ваших маршрутов</p>
<div class="hero-content">
<div class="hero-text">
<h1 class="hero-title">Поиск объектов для путешествий</h1>
<p class="hero-subtitle">Найдите лучшие места для ваших маршрутов</p>
<!-- Простой поиск -->
<div class="max-w-xl mx-auto mb-12">
<div class="flex gap-2">
<!-- Поисковая строка -->
<div class="search-container">
<div class="search-box">
<input
type="text"
placeholder="Поиск объектов..."
class="form-input flex-1"
placeholder="Введите название, город или описание..."
class="search-input"
@keyup.enter="handleSearch"
v-model="searchQuery"
>
<button @click="handleSearch" class="btn btn-primary">
<button @click="handleSearch" class="btn btn-primary search-button">
Найти
</button>
</div>
</div>
<!-- Кнопки авторизации -->
<div class="flex gap-4 justify-center">
<NuxtLink to="/auth/login" class="btn btn-outline text-lg px-8 py-3">
Войти
</NuxtLink>
<NuxtLink to="/auth/register" class="btn btn-primary text-lg px-8 py-3">
Регистрация
</NuxtLink>
</div>
</div>
</div>
</div>
</section>
<!-- Быстрая навигация -->
<section class="py-16 bg-white">
<div class="container">
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-4xl mx-auto">
<NuxtLink
to="/objects"
class="category-card text-center group"
>
<div class="category-card__icon group-hover:bg-primary-200">
🔍
</div>
<h3 class="font-semibold text-lg mb-2">Все объекты</h3>
<p class="text-sm text-gray-600">Просмотр всей базы объектов</p>
</NuxtLink>
<NuxtLink
to="/objects/my-objects"
class="category-card text-center group"
>
<div class="category-card__icon group-hover:bg-primary-200">
📝
</div>
<h3 class="font-semibold text-lg mb-2">Мои объекты</h3>
<p class="text-sm text-gray-600">Управление вашими объектами</p>
</NuxtLink>
<NuxtLink
to="/objects/create"
class="category-card text-center group"
>
<div class="category-card__icon group-hover:bg-primary-200">
</div>
<h3 class="font-semibold text-lg mb-2">Добавить объект</h3>
<p class="text-sm text-gray-600">Создать новую запись</p>
</NuxtLink>
</div>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import Header from '~/components/layout/Header.vue'
const searchQuery = ref('')
const handleSearch = () => {
@@ -91,3 +46,136 @@ const handleSearch = () => {
}
}
</script>
<style scoped>
.page-wrapper {
min-height: 100vh;
display: flex;
flex-direction: column;
}
.hero-section {
background: linear-gradient(135deg, var(--primary-500) 0%, var(--primary-700) 100%);
color: var(--text-inverse);
position: relative;
overflow: hidden;
flex: 1;
display: flex;
align-items: center;
}
.hero-section::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><polygon fill="%230ea5e9" points="0,1000 1000,0 1000,1000"/></svg>');
opacity: 0.1;
}
.hero-content {
position: relative;
z-index: 10;
padding: 2rem 0;
width: 100%;
}
.hero-text {
max-width: 800px;
margin: 0 auto;
text-align: center;
}
.hero-title {
font-size: 3rem;
font-weight: bold;
margin-bottom: 1.5rem;
line-height: 1.2;
}
.hero-subtitle {
font-size: 1.25rem;
opacity: 0.9;
margin-bottom: 3rem;
}
.search-container {
max-width: 800px;
margin: 0 auto;
}
.search-box {
display: flex;
gap: 0.5rem;
background: var(--bg-primary);
border-radius: var(--radius-lg);
padding: 0.5rem;
box-shadow: var(--shadow-lg);
}
.search-input {
flex: 1;
border: none;
border-radius: var(--radius-md);
padding: 1rem;
font-size: 1.125rem;
background: var(--bg-primary);
color: var(--text-primary);
min-width: 0; /* Важно для правильного сжатия в flex */
}
.search-input:focus {
outline: none;
}
.search-input::placeholder {
color: var(--text-tertiary);
}
.search-button {
padding: 1rem 2rem;
font-size: 1.125rem;
white-space: nowrap;
flex-shrink: 0;
}
@media (max-width: 768px) {
.hero-title {
font-size: 2rem;
}
.hero-subtitle {
font-size: 1rem;
margin-bottom: 2rem;
}
.search-box {
flex-direction: column;
}
.search-button {
width: 100%;
}
.hero-content {
padding: 1rem 0;
}
}
@media (max-width: 480px) {
.hero-title {
font-size: 1.75rem;
}
.hero-subtitle {
font-size: 0.9rem;
}
.search-input {
padding: 0.75rem;
font-size: 1rem;
}
}
</style>
@@ -40,44 +40,44 @@
</template>
<script setup lang="ts">
definePageMeta({
middleware: 'auth'
})
// definePageMeta({
// middleware: 'auth'
// })
const route = useRoute()
const { fetchObjectById, updateObject } = useObjects()
const object = ref(null)
const loading = ref(true)
const updating = ref(false)
// const route = useRoute()
// const { fetchObjectById, updateObject } = useObjects()
// const object = ref(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
})
// onMounted(async () => {
// const objectId = parseInt(route.params.id as string)
// if (objectId) {
// const result = await fetchObjectById(objectId)
// object.value = result
// }
// loading.value = false
// })
const handleSubmit = async (formData: any) => {
updating.value = true
// const handleSubmit = async (formData: any) => {
// updating.value = true
try {
const objectId = parseInt(route.params.id as string)
await updateObject(objectId, formData)
// try {
// const objectId = parseInt(route.params.id as string)
// await updateObject(objectId, formData)
alert('Объект успешно обновлен!')
await navigateTo(`/objects/${objectId}`)
} catch (error) {
console.error('Error updating object:', error)
alert('Ошибка при обновлении объекта')
} finally {
updating.value = false
}
}
// alert('Объект успешно обновлен!')
// await navigateTo(`/objects/${objectId}`)
// } catch (error) {
// console.error('Error updating object:', error)
// alert('Ошибка при обновлении объекта')
// } finally {
// updating.value = false
// }
// }
const handleCancel = () => {
const objectId = route.params.id
navigateTo(`/objects/${objectId}`)
}
// const handleCancel = () => {
// const objectId = route.params.id
// navigateTo(`/objects/${objectId}`)
// }
</script>
@@ -16,37 +16,37 @@
</template>
<script setup lang="ts">
definePageMeta({
middleware: 'auth'
})
// definePageMeta({
// middleware: 'auth'
// })
const { createObject } = useObjects()
const loading = ref(false)
// const { createObject } = useObjects()
// const loading = ref(false)
const handleSubmit = async (formData: any) => {
loading.value = true
// const handleSubmit = async (formData: any) => {
// loading.value = true
try {
await createObject({
...formData,
userId: 1, // В реальном приложении из авторизации
isActive: true,
images: formData.images || ['/images/placeholder.jpg'],
amenities: formData.amenities || []
})
// 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
}
}
// // Показываем уведомление об успехе
// 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,36 +1,28 @@
<template>
<div class="min-h-screen bg-gray-50 py-8">
<div class="container">
<!-- Заголовок и статистика -->
<!-- Заголовок -->
<div class="mb-8">
<div class="flex justify-between items-center mb-6">
<div>
<h1 class="text-3xl font-bold">Мои объекты</h1>
<p class="text-gray-600 mt-2">Управление вашими добавленными местами</p>
</div>
<div class="flex gap-3">
<NuxtLink to="/objects" class="btn btn-outline">
🔍 Все объекты
</NuxtLink>
<NuxtLink to="/objects/create" class="btn btn-primary">
Добавить объект
</NuxtLink>
</div>
</div>
<!-- Статистика -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<div class="card text-center">
<div class="text-2xl font-bold text-primary-600">{{ stats.total }}</div>
<div class="text-gray-600">Всего объектов</div>
</div>
<div class="card text-center">
<div class="text-2xl font-bold text-green-600">{{ stats.active }}</div>
<div class="text-gray-600">Активных</div>
</div>
<div class="card text-center">
<div class="text-2xl font-bold text-blue-600">{{ stats.rating }}</div>
<div class="text-gray-600">Средний рейтинг</div>
</div>
<div class="card text-center">
<div class="text-2xl font-bold text-orange-600">{{ stats.reviews }}</div>
<div class="text-gray-600">Отзывов</div>
</div>
<!-- Информация -->
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
<p class="text-blue-700">
💡 Это демо-режим. В реальном приложении здесь будут отображаться только ваши объекты.
</p>
</div>
</div>
@@ -119,64 +111,30 @@
</NuxtLink>
</div>
</div>
<!-- Навигация -->
<div class="mt-8 flex justify-between">
<NuxtLink to="/objects" class="btn btn-outline">
Все объекты
</NuxtLink>
<NuxtLink to="/" class="btn btn-outline">
🏠 На главную
</NuxtLink>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ObjectItem } from '~/composables/useObjects'
// import { ObjectItem } from '~/composables/useObjects'
definePageMeta({
middleware: 'auth'
})
// // Убрали middleware - страница доступна всем
const { fetchMyObjects, deleteObject, objects: myObjects, loading } = useObjects()
// const { fetchMyObjects, deleteObject, objects: myObjects, loading } = useObjects()
// Статистика (мок-данные)
const stats = ref({
total: 0,
active: 0,
rating: 4.5,
reviews: 0
})
// onMounted(async () => {
// await fetchMyObjects()
// })
onMounted(async () => {
await fetchMyObjects()
updateStats()
})
const updateStats = () => {
stats.value.total = myObjects.value.length
stats.value.active = myObjects.value.filter(obj => obj.isActive).length
stats.value.reviews = myObjects.value.reduce((sum, obj) => sum + (obj.reviewCount || 0), 0)
}
const getTypeLabel = (type: string) => {
const types: Record<string, string> = {
hotel: 'Отель',
apartment: 'Апартаменты',
villa: 'Вилла',
camping: 'Кемпинг',
restaurant: 'Ресторан',
attraction: 'Достопримечательность'
}
return types[type] || type
}
const formatPrice = (price: number) => {
return new Intl.NumberFormat('ru-RU').format(price) + ' ₽'
}
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('ru-RU')
}
// Обработка удаления объекта
const handleDeleteObject = async (id: number) => {
if (confirm('Вы уверены, что хотите удалить этот объект?')) {
await deleteObject(id)
await fetchMyObjects()
updateStats()
}
}
// ... остальные функции без изменений ...
</script>
@@ -1,6 +1,300 @@
<template>
<div>
<h1>Страница</h1>
<p>Содержимое страницы</p>
<div class="profile-page">
<div class="container container-large">
<div class="page-header">
<h1 class="page-title">Профиль пользователя</h1>
<p class="page-subtitle">Информация о вашем аккаунте</p>
</div>
<div class="profile-layout">
<!-- Основная информация -->
<div class="profile-main">
<div class="card">
<div class="card-header">
<h2 class="card-title">Основная информация</h2>
</div>
<div class="card-body">
<div class="user-info">
<div class="user-avatar-section">
<div class="user-avatar">
<span class="avatar-text">ИИ</span>
</div>
<div class="user-details">
<div class="user-name">Иван Иванов</div>
<div class="user-email">user@example.com</div>
</div>
</div>
<div class="user-stats">
<div class="stat-item">
<div class="stat-number">5</div>
<div class="stat-label">Объектов</div>
</div>
<div class="stat-item">
<div class="stat-number">12</div>
<div class="stat-label">Отзывов</div>
</div>
</div>
</div>
</div>
</div>
<!-- Быстрые действия -->
<div class="card">
<div class="card-header">
<h2 class="card-title">Быстрые действия</h2>
</div>
<div class="card-body">
<div class="actions-grid">
<NuxtLink to="/objects/my-objects" class="btn btn-outline action-btn">
📝 Мои объекты
</NuxtLink>
<NuxtLink to="/objects/create" class="btn btn-primary action-btn">
Добавить объект
</NuxtLink>
<NuxtLink to="/objects" class="btn btn-outline action-btn">
🔍 Все объекты
</NuxtLink>
<NuxtLink to="/" class="btn btn-outline action-btn">
🏠 Главная
</NuxtLink>
</div>
</div>
</div>
</div>
<!-- Боковая панель -->
<div class="profile-sidebar">
<!-- Статус -->
<div class="card">
<div class="card-header">
<h3 class="card-title">Статус аккаунта</h3>
</div>
<div class="card-body">
<div class="status-item">
<span class="status-icon"></span>
<span class="status-text">Активен</span>
</div>
<p class="status-note">Демо-режим</p>
</div>
</div>
<!-- Навигация -->
<div class="card">
<div class="card-header">
<h3 class="card-title">Навигация</h3>
</div>
<div class="card-body">
<div class="nav-links">
<NuxtLink to="/objects" class="nav-link-block">
🔍 Все объекты
</NuxtLink>
<NuxtLink to="/objects/my-objects" class="nav-link-block">
📝 Мои объекты
</NuxtLink>
<NuxtLink to="/objects/create" class="nav-link-block">
Добавить объект
</NuxtLink>
<NuxtLink to="/auth/login" class="nav-link-block">
🔑 Войти
</NuxtLink>
<NuxtLink to="/auth/register" class="nav-link-block">
📝 Регистрация
</NuxtLink>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
// Страница профиля доступна всем в демо-режиме
</script>
<style scoped>
.profile-page {
min-height: 100vh;
background: var(--bg-secondary);
padding: 2rem 0;
}
.container-large {
max-width: 1200px;
}
.page-header {
margin-bottom: 2rem;
}
.page-title {
font-size: 2rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.page-subtitle {
color: var(--text-secondary);
font-size: 1.125rem;
}
.profile-layout {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
}
.profile-main {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.profile-sidebar {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.user-info {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.user-avatar-section {
display: flex;
align-items: center;
gap: 1rem;
}
.user-avatar {
width: 4rem;
height: 4rem;
background: var(--primary-100);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-text {
color: var(--primary-600);
font-weight: bold;
font-size: 1.25rem;
}
.user-details {
display: flex;
flex-direction: column;
}
.user-name {
font-size: 1.25rem;
font-weight: 600;
}
.user-email {
color: var(--text-secondary);
}
.user-stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
padding-top: 1rem;
border-top: 1px solid var(--border-light);
}
.stat-item {
text-align: center;
}
.stat-number {
font-size: 2rem;
font-weight: bold;
color: var(--primary-600);
}
.stat-label {
color: var(--text-secondary);
font-size: 0.875rem;
}
.actions-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.action-btn {
display: flex;
justify-content: center;
width: 100%;
}
.status-item {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.status-icon {
font-size: 1.125rem;
}
.status-text {
font-weight: 500;
color: var(--success-600);
}
.status-note {
color: var(--text-secondary);
font-size: 0.875rem;
}
.nav-links {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.nav-link-block {
display: block;
padding: 0.75rem 1rem;
border-radius: var(--radius-md);
color: var(--text-secondary);
text-decoration: none;
transition: all 0.2s;
font-weight: 500;
}
.nav-link-block:hover {
color: var(--primary-600);
background: var(--primary-50);
}
@media (max-width: 968px) {
.profile-layout {
grid-template-columns: 1fr;
}
.actions-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.user-avatar-section {
flex-direction: column;
text-align: center;
}
.page-title {
font-size: 1.5rem;
}
}
</style>
@@ -56,6 +56,7 @@ export default defineNuxtConfig({
css: {
devSourcemap: false
}
}
},
})