Files
tp/main_dc/yalarba/easySite/easySite/app/pages/objects/my-objects.vue
T
valitovgaziz f78a23ff38 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
add header footer components into new pages
2025-11-07 14:36:10 +05:00

529 lines
13 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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="page-wrapper">
<Header />
<main class="my-objects-page">
<div class="container">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<div class="header-text">
<h1 class="page-title">Мои объекты</h1>
<p class="page-subtitle">Управление вашими добавленными местами</p>
</div>
<div class="header-actions">
<NuxtLink to="/objects" class="btn btn-outline btn-with-icon">
<span>🔍</span>
Все объекты
</NuxtLink>
<NuxtLink to="/objects/create" class="btn btn-primary btn-with-icon">
<span></span>
Добавить объект
</NuxtLink>
</div>
</div>
</div>
<!-- Фильтры -->
<div class="search-filters card">
<div class="filter-grid">
<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>
<option value="guest_house">🏡 Гостевой дом</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="filter-actions">
<button class="btn btn-primary" @click="applyFilters">
Применить
</button>
<button class="btn btn-outline" @click="resetFilters">
Сбросить
</button>
</div>
</div>
<!-- Таблица объектов -->
<div class="objects-table card">
<div class="card-header">
<h2 class="card-title">Список объектов</h2>
<p class="card-subtitle">Всего: {{ myObjects.length }} объектов</p>
</div>
<div class="table-container">
<table class="objects-table">
<thead>
<tr>
<th class="table-header">Название</th>
<th class="table-header">Тип</th>
<th class="table-header">Город</th>
<th class="table-header">Цена</th>
<th class="table-header">Статус</th>
<th class="table-header">Действия</th>
</tr>
</thead>
<tbody>
<tr v-for="object in myObjects" :key="object.id" class="table-row">
<td class="table-cell">
<div class="object-info">
<div class="object-title">{{ object.title }}</div>
<div class="object-date">{{ formatDate(object.createdAt) }}</div>
</div>
</td>
<td class="table-cell">
<span class="badge badge-primary">
{{ getTypeLabel(object.type) }}
</span>
</td>
<td class="table-cell">{{ object.city }}</td>
<td class="table-cell">
<div class="object-price">{{ formatPrice(object.price) }}</div>
</td>
<td class="table-cell">
<span class="status-badge" :class="object.isActive ? 'status-active' : 'status-inactive'">
{{ object.isActive ? 'Активен' : 'Неактивен' }}
</span>
</td>
<td class="table-cell">
<div class="action-buttons">
<NuxtLink :to="`/objects/${object.id}`" class="btn btn-outline btn-sm btn-with-icon"
title="Просмотр">
👁
</NuxtLink>
<NuxtLink :to="`/objects/${object.id}/edit`" class="btn btn-outline btn-sm btn-with-icon"
title="Редактировать">
</NuxtLink>
<button @click="deleteObject(object.id)" class="btn btn-outline btn-sm btn-with-icon delete-btn"
title="Удалить">
🗑
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Пустой state -->
<div v-if="!loading && myObjects.length === 0" class="empty-state">
<div class="empty-icon">🏢</div>
<h3 class="empty-title">У вас пока нет объектов</h3>
<p class="empty-description">Добавьте первый объект, чтобы начать работу</p>
<NuxtLink to="/objects/create" class="btn btn-primary">
Добавить первый объект
</NuxtLink>
</div>
</div>
<!-- Навигация -->
<div class="page-navigation">
<NuxtLink to="/objects" class="btn btn-outline btn-with-icon">
Все объекты
</NuxtLink>
<NuxtLink to="/" class="btn btn-outline btn-with-icon">
🏠 На главную
</NuxtLink>
</div>
</div>
</main>
<Footer />
</div>
</template>
<script setup lang="ts">
import Header from '~/components/layout/Header.vue'
import Footer from '~/components/layout/Footer.vue'
interface MyObjectItem {
id: number
title: string
type: string
city: string
price: number
isActive: boolean
createdAt: string
}
// Мок-данные
const mockMyObjects: MyObjectItem[] = [
{
id: 1,
title: 'Гостевой дом "У озера"',
type: 'guest_house',
city: 'Карелия',
price: 2800,
isActive: true,
createdAt: '2024-01-15'
},
{
id: 2,
title: 'Экскурсия по историческому центру',
type: 'tour',
city: 'Санкт-Петербург',
price: 1500,
isActive: true,
createdAt: '2024-01-10'
},
{
id: 3,
title: 'Горнолыжный курорт "Снежный"',
type: 'hotel',
city: 'Красная Поляна',
price: 5200,
isActive: false,
createdAt: '2024-01-05'
}
]
const myObjects = ref<MyObjectItem[]>(mockMyObjects)
const loading = ref(false)
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>
<style scoped>
.my-objects-page {
padding: var(--space-xl) 0;
min-height: calc(100vh - 160px);
background: var(--bg-secondary);
}
.page-header {
background: var(--bg-primary);
border-radius: var(--radius-lg);
padding: var(--space-xl);
margin-bottom: var(--space-lg);
box-shadow: var(--shadow-sm);
border: 1px solid var(--border-light);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: var(--space-lg);
}
.header-text {
flex: 1;
}
.page-title {
font-family: var(--font-heading);
font-size: var(--text-3xl);
font-weight: var(--font-bold);
color: var(--text-primary);
margin-bottom: var(--space-xs);
}
.page-subtitle {
font-size: var(--text-lg);
color: var(--text-secondary);
}
.header-actions {
display: flex;
gap: var(--space-sm);
flex-wrap: wrap;
}
.filter-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--space-lg);
margin-bottom: var(--space-lg);
}
.filter-actions {
display: flex;
gap: var(--space-sm);
flex-wrap: wrap;
}
.objects-table {
width: 100%;
}
.card-header {
padding: var(--space-xl);
border-bottom: 1px solid var(--border-light);
background: var(--bg-primary);
}
.card-title {
font-family: var(--font-heading);
font-size: var(--text-xl);
font-weight: var(--font-semibold);
color: var(--text-primary);
margin-bottom: var(--space-xs);
}
.card-subtitle {
color: var(--text-secondary);
font-size: var(--text-sm);
}
.table-container {
overflow-x: auto;
}
.objects-table {
width: 100%;
border-collapse: collapse;
}
.table-header {
padding: var(--space-md);
text-align: left;
font-weight: var(--font-semibold);
color: var(--text-secondary);
border-bottom: 1px solid var(--border-light);
background: var(--bg-secondary);
font-size: var(--text-sm);
text-transform: uppercase;
letter-spacing: var(--tracking-wide);
}
.table-row {
border-bottom: 1px solid var(--border-light);
transition: background-color 0.3s ease;
}
.table-row:hover {
background: var(--bg-secondary);
}
.table-cell {
padding: var(--space-md);
vertical-align: middle;
}
.object-info {
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.object-title {
font-weight: var(--font-semibold);
color: var(--text-primary);
}
.object-date {
font-size: var(--text-sm);
color: var(--text-tertiary);
}
.object-price {
font-weight: var(--font-semibold);
color: var(--primary-600);
font-size: var(--text-lg);
}
.status-badge {
padding: var(--space-xs) var(--space-sm);
border-radius: var(--radius-sm);
font-size: var(--text-xs);
font-weight: var(--font-medium);
text-transform: uppercase;
letter-spacing: var(--tracking-wide);
}
.status-active {
background: var(--success-50);
color: var(--success-600);
}
.status-inactive {
background: var(--gray-100);
color: var(--gray-600);
}
.action-buttons {
display: flex;
gap: var(--space-xs);
}
.delete-btn:hover {
background: var(--error-50);
border-color: var(--error-300);
color: var(--error-600);
}
.empty-state {
text-align: center;
padding: var(--space-2xl);
}
.empty-icon {
font-size: 4rem;
margin-bottom: var(--space-lg);
opacity: 0.5;
}
.empty-title {
font-family: var(--font-heading);
font-size: var(--text-xl);
font-weight: var(--font-semibold);
color: var(--text-primary);
margin-bottom: var(--space-sm);
}
.empty-description {
color: var(--text-secondary);
margin-bottom: var(--space-lg);
}
.page-navigation {
display: flex;
justify-content: space-between;
margin-top: var(--space-xl);
padding-top: var(--space-lg);
border-top: 1px solid var(--border-light);
}
/* Адаптивность */
@media (max-width: 768px) {
.header-content {
flex-direction: column;
align-items: stretch;
}
.header-actions {
justify-content: stretch;
}
.header-actions .btn {
flex: 1;
min-width: auto;
}
.filter-grid {
grid-template-columns: 1fr;
}
.table-container {
font-size: var(--text-sm);
}
.action-buttons {
flex-direction: column;
}
.page-navigation {
flex-direction: column;
gap: var(--space-sm);
}
.page-navigation .btn {
justify-content: center;
}
}
@media (max-width: 480px) {
.page-header {
padding: var(--space-lg);
}
.page-title {
font-size: var(--text-2xl);
}
.card-header {
padding: var(--space-lg);
}
.table-cell {
padding: var(--space-sm);
}
.objects-table {
font-size: var(--text-xs);
}
}
</style>