Files
tp/main_dc/yalarba/easySite/app/pages/profile/index.vue
T
valitovgaziz 2941b14b38 flatten easySite directory: remove extra easySite/easySite nesting
- Moved contents of main_dc/yalarba/easySite/easySite/ up to easySite/
- Updated docker-compose.yml build context path
- Deleted empty nested easySite/ directory
2026-06-12 11:16:15 +05:00

583 lines
16 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="profile-page">
<div class="container container-large">
<div class="page-header">
<h1 class="page-title">Профиль пользователя</h1>
<p class="page-subtitle">Информация о вашем аккаунте</p>
</div>
<div v-if="auth.user.value" 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">{{ userInitials }}</span>
</div>
<div class="user-details">
<div class="user-name">{{ auth.user.value.full_name || 'Не указано' }}</div>
<div class="user-email">{{ auth.user.value.email || 'Не указано' }}</div>
</div>
</div>
<div class="info-grid">
<div class="info-item">
<label class="info-label">Полное имя</label>
<div class="info-value">{{ auth.user.value.full_name || 'Не указано' }}</div>
</div>
<div class="info-item">
<label class="info-label">ИНН</label>
<div class="info-value">{{ auth.user.value.inn || 'Не указано' }}</div>
</div>
<div class="info-item">
<label class="info-label">Телефон</label>
<div class="info-value">{{ auth.user.value.phone || 'Не указано' }}</div>
</div>
<div class="info-item">
<label class="info-label">Город</label>
<div class="info-value">{{ auth.user.value.city || 'Не указано' }}</div>
</div>
</div>
</div>
</div>
</div>
<!-- Информация об организации -->
<div v-if="hasOrganizationInfo" class="card">
<div class="card-header">
<h2 class="card-title">Информация об организации</h2>
</div>
<div class="card-body">
<div class="org-info">
<div class="info-grid">
<div class="info-item">
<label class="info-label">Форма организации</label>
<div class="info-value">{{ orgTypeText }}</div>
</div>
<div class="info-item">
<label class="info-label">Полное название организации</label>
<div class="info-value">{{ auth.user.value.org_full_name || 'Не указано' }}</div>
</div>
<div class="info-item">
<label class="info-label">Короткое название организации</label>
<div class="info-value">{{ auth.user.value.org_short_name || 'Не указано' }}</div>
</div>
<div class="info-item">
<label class="info-label">ИНН организации</label>
<div class="info-value">{{ auth.user.value.org_inn || 'Не указано' }}</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="user-stats">
<div class="stat-item">
<div class="stat-number">{{ userStats.objects }}</div>
<div class="stat-label">Объектов</div>
</div>
<div class="stat-item">
<div class="stat-number">{{ userStats.reviews }}</div>
<div class="stat-label">Отзывов</div>
</div>
<div class="stat-item">
<div class="stat-number">{{ userStats.active }}</div>
<div class="stat-label">Активных</div>
</div>
<div class="stat-item">
<div class="stat-number">{{ userStats.moderation }}</div>
<div class="stat-label">На модерации</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="/profile/edit" 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>
<div v-if="auth.user.value.org_type" class="status-item">
<span class="status-icon">🏢</span>
<span class="status-text">{{ orgTypeText }}</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="contact-info">
<div class="contact-item">
<label class="contact-label">Телефон</label>
<div class="contact-value">{{ auth.user.value.phone || 'Не указано' }}</div>
</div>
<div class="contact-item">
<label class="contact-label">Email</label>
<div class="contact-value">{{ auth.user.value.email || 'Не указано' }}</div>
</div>
<div class="contact-item">
<label class="contact-label">Город</label>
<div class="contact-value">{{ auth.user.value.city || 'Не указано' }}</div>
</div>
</div>
</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="/profile/edit" class="nav-link-block">
Редактировать профиль
</NuxtLink>
<button class="nav-link-block logout-btn" @click="auth.logout()">
🚪 Выйти
</button>
<NuxtLink to="/" class="nav-link-block">
🏠 Главная
</NuxtLink>
</div>
</div>
</div>
</div>
</div>
<!-- Загрузка -->
<div v-else-if="auth.loading.value" class="loading-container">
<div class="loading-spinner" />
<p>Загрузка профиля...</p>
</div>
<!-- Ошибка -->
<div v-else class="error-container">
<p>Не удалось загрузить данные профиля</p>
<button class="btn btn-primary" @click="reload">Попробовать снова</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
definePageMeta({
middleware: 'auth',
requiresAuth: true
})
const auth = useAuth()
// Инициализация данных пользователя
onMounted(async () => {
if (!auth.user.value) {
await auth.checkAuth()
}
})
// Вычисляем инициалы пользователя для аватара
const userInitials = computed(() => {
if (!auth.user.value?.full_name) return '??'
const names = auth.user.value.full_name.split(' ')
if (names.length >= 2 && names[0] && names[1]) {
return `${names[0][0]}${names[1][0]}`.toUpperCase()
}
return auth.user.value.full_name.substring(0, 2).toUpperCase()
})
// Текст для типа организации
const orgTypeText = computed(() => {
const types: Record<string, string> = {
individual: 'Индивидуальный предприниматель',
llc: 'Общество с ограниченной ответственностью',
jsc: 'Акционерное общество'
}
return types[auth.user.value?.org_type || ''] || 'Не указано'
})
// Проверяем, есть ли информация об организации
const hasOrganizationInfo = computed(() => {
return auth.user.value?.org_type ||
auth.user.value?.org_full_name ||
auth.user.value?.org_short_name ||
auth.user.value?.org_inn
})
// Статистика пользователя (заглушка - в реальном приложении нужно получать с API)
const userStats = computed(() => {
return {
objects: auth.user.value?.objects_count || 0,
reviews: auth.user.value?.reviews_count || 0,
active: auth.user.value?.active_objects_count || 0,
moderation: auth.user.value?.moderation_objects_count || 0
}
})
const reload = () => {
window.location.reload()
}
</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);
}
.info-grid {
display: flex;
flex-direction: column;
gap: 1rem;
}
.info-item {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.info-label {
font-size: 0.875rem;
color: var(--text-secondary);
font-weight: 500;
}
.info-value {
font-weight: 500;
color: var(--text-primary);
}
.org-info {
display: flex;
flex-direction: column;
gap: 1rem;
}
.contact-info {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.contact-item {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.contact-label {
font-size: 0.75rem;
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.contact-value {
font-weight: 500;
color: var(--text-primary);
}
.user-stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
.stat-item {
text-align: center;
padding: 1rem;
background: var(--bg-secondary);
border-radius: var(--radius-md);
}
.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.75rem;
}
.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;
margin-top: 0.5rem;
padding-top: 0.5rem;
border-top: 1px solid var(--border-light);
}
.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;
border: none;
background: none;
width: 100%;
text-align: left;
cursor: pointer;
}
.nav-link-block:hover {
color: var(--primary-600);
background: var(--primary-50);
}
.logout-btn:hover {
color: var(--error-600);
background: var(--error-50);
}
.loading-container,
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem 2rem;
text-align: center;
}
.loading-spinner {
width: 3rem;
height: 3rem;
border: 3px solid var(--border-light);
border-top: 3px solid var(--primary-500);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@media (max-width: 968px) {
.profile-layout {
grid-template-columns: 1fr;
}
.actions-grid {
grid-template-columns: 1fr;
}
.user-stats {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 768px) {
.user-avatar-section {
flex-direction: column;
text-align: center;
}
.page-title {
font-size: 1.5rem;
}
.user-stats {
grid-template-columns: 1fr;
gap: 1rem;
}
}
@media (max-width: 480px) {
.profile-page {
padding: 1rem 0;
}
.page-header {
margin-bottom: 1.5rem;
}
.profile-main,
.profile-sidebar {
gap: 1rem;
}
}
</style>