88046f722d
modified: serv_nginx/bbvue/src/views/Profile.vue change GET path for personal-bests
1052 lines
26 KiB
Vue
1052 lines
26 KiB
Vue
<template>
|
||
<div class="page">
|
||
<h2>👤 Бегущий башкир: </h2>
|
||
<h1>{{ user.firstName }} {{ user.lastName }}</h1>
|
||
|
||
<div v-if="authLoading" class="loading">Загрузка профиля...</div>
|
||
|
||
<div v-else-if="user" class="profile-content">
|
||
<div class="profile-header">
|
||
<!-- Секция аватара -->
|
||
<div class="avatar-section">
|
||
<div class="avatar-preview">
|
||
<img v-if="user.avatar && !avatarLoadError" :src="avatarUrl"
|
||
:alt="`Аватар ${user.firstName} ${user.lastName}`" class="avatar-image" @error="handleAvatarError">
|
||
<div v-else class="avatar-placeholder">
|
||
👤
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<p>Участник с {{ joinDate }}</p>
|
||
<p class="user-email">{{ user.email }}</p>
|
||
<p v-if="user.phone" class="user-phone">📱 {{ user.phone }}</p>
|
||
</div>
|
||
|
||
<!-- Основная информация -->
|
||
<div class="profile-info">
|
||
<h3>📋 Информация о пользователе</h3>
|
||
<div class="info-grid">
|
||
<div class="info-item">
|
||
<label>Уровень подготовки:</label>
|
||
<span class="info-value">{{ experienceLabel }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<label>Цели:</label>
|
||
<span class="info-value">{{ goalsLabel }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<label>Рассылка:</label>
|
||
<span class="info-value">{{ user.newsletter ? '✅ Подключена' : '❌ Отключена' }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<label>Роль:</label>
|
||
<span class="info-value role-badge">{{ user.role }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Статистика -->
|
||
<div class="profile-stats">
|
||
<div class="stats-header">
|
||
<h3>📊 Моя статистика</h3>
|
||
<button class="btn-refresh" @click="refreshStats" :disabled="statsLoading">
|
||
{{ statsLoading ? '⟳' : '🔄' }}
|
||
</button>
|
||
</div>
|
||
|
||
<div v-if="statsError" class="error-message">
|
||
{{ statsError }}
|
||
</div>
|
||
|
||
<div v-else class="stats-grid">
|
||
<div class="stat-card">
|
||
<h4>🏃 Всего пробег</h4>
|
||
<p>{{ userStats?.totalDistance || 0 }} км</p>
|
||
</div>
|
||
<div class="stat-card">
|
||
<h4>⏱️ Общее время</h4>
|
||
<p>{{ formatTime(userStats?.totalTime) }}</p>
|
||
</div>
|
||
<div class="stat-card">
|
||
<h4>📅 Тренировок</h4>
|
||
<p>{{ userStats?.workoutsCount || 0 }}</p>
|
||
</div>
|
||
<div class="stat-card">
|
||
<h4>🔥 Текущая серия</h4>
|
||
<p>{{ userStats?.currentStreak || 0 }} дней</p>
|
||
</div>
|
||
<div class="stat-card">
|
||
<h4>⭐ Лучшая серия</h4>
|
||
<p>{{ userStats?.longestStreak || 0 }} дней</p>
|
||
</div>
|
||
<div class="stat-card">
|
||
<h4>📈 Пробег за неделю</h4>
|
||
<p>{{ userStats?.weeklyDistance || 0 }} км</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Личные рекорды -->
|
||
<div class="personal-bests-section" v-if="personalBests.length > 0">
|
||
<h3>⭐ Личные рекорды</h3>
|
||
<div class="bests-grid">
|
||
<div v-for="best in personalBests" :key="best.id" class="best-card">
|
||
<div class="best-type">{{ getDistanceLabel(best.distanceType) }}</div>
|
||
<div class="best-time">{{ best.time }}</div>
|
||
<div class="best-date">{{ formatDate(best.date) }}</div>
|
||
<div v-if="best.eventName" class="best-event">{{ best.eventName }}</div>
|
||
<span v-if="best.verified" class="verified-badge">✅ Проверен</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Ближайшие события -->
|
||
<div class="events-section" v-if="upcomingEvents.length > 0">
|
||
<h3>📅 Ближайшие события</h3>
|
||
<div class="events-list">
|
||
<div v-for="event in upcomingEvents" :key="event.id" class="event-card">
|
||
<div class="event-date">{{ formatEventDate(event.date) }}</div>
|
||
<div class="event-title">{{ event.title }}</div>
|
||
<div class="event-location">📍 {{ event.location }}</div>
|
||
<div class="event-distance" v-if="event.distance">{{ event.distance }}</div>
|
||
<span class="event-type" :class="event.type">{{ getEventTypeLabel(event.type) }}</span>
|
||
</div>
|
||
</div>
|
||
<button class="btn btn-outline" @click="$router.push('/events')">
|
||
📋 Все события
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Достижения -->
|
||
<div class="achievements-preview">
|
||
<h3>🏆 Достижения</h3>
|
||
<div class="achievements-progress">
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" :style="{ width: achievementProgress + '%' }"></div>
|
||
</div>
|
||
<span>{{ achievementProgress }}% выполнено</span>
|
||
</div>
|
||
<div class="achievements-count">
|
||
<span>Получено: {{ completedAchievements.length }} из {{ userAchievements.length }}</span>
|
||
</div>
|
||
|
||
<div v-if="recentAchievements.length > 0" class="recent-achievements">
|
||
<h4>Последние достижения:</h4>
|
||
<div class="achievements-list">
|
||
<div v-for="achievement in recentAchievements" :key="achievement.id" class="achievement-badge">
|
||
<span class="achievement-icon">🏅</span>
|
||
<span class="achievement-title">{{ achievement.title }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button class="btn btn-outline" @click="$router.push('/achievements')">
|
||
📜 Все достижения
|
||
</button>
|
||
</div>
|
||
|
||
<!-- План тренировок -->
|
||
<div class="training-plan-section" v-if="currentTrainingPlan">
|
||
<h3>📅 Мой план тренировок</h3>
|
||
<div class="plan-card">
|
||
<div class="plan-title">{{ currentTrainingPlan.title }}</div>
|
||
<div class="plan-progress">
|
||
<div class="progress-info">
|
||
<span>Неделя {{ currentTrainingPlan.currentWeek }} из {{ currentTrainingPlan.weeks }}</span>
|
||
<span>{{ Math.round((currentTrainingPlan.currentWeek / currentTrainingPlan.weeks) * 100) }}%</span>
|
||
</div>
|
||
<div class="progress-bar">
|
||
<div class="progress-fill"
|
||
:style="{ width: Math.round((currentTrainingPlan.currentWeek / currentTrainingPlan.weeks) * 100) + '%' }">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="plan-target" v-if="currentTrainingPlan.targetDistance">
|
||
Цель: {{ currentTrainingPlan.targetDistance }}
|
||
</div>
|
||
<button class="btn" @click="$router.push('/training')">
|
||
📊 Продолжить тренировки
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Действия -->
|
||
<div class="profile-actions">
|
||
<button class="btn" @click="editProfile">✏️ Редактировать профиль</button>
|
||
<button class="btn" @click="viewDetailedStats">📊 Подробная статистика</button>
|
||
<button class="btn" @click="$router.push('/personal-bests')">⭐ Мои рекорды</button>
|
||
<button class="btn" @click="$router.push('/events')">📅 События</button>
|
||
<button class="btn btn-logout" @click="handleLogout" :disabled="authLoading">
|
||
{{ authLoading ? 'Выход...' : '🚪 Выйти' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-else class="error-message">
|
||
Не удалось загрузить данные профиля.
|
||
<router-link to="/login" class="link">Войдите</router-link> снова.
|
||
</div>
|
||
|
||
<button class="btn btn-secondary" @click="$router.push('/')">← На главную</button>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { useAuthStore } from '../stores/auth'
|
||
import { useUserStore } from '../stores/user'
|
||
|
||
export default {
|
||
// eslint-disable-next-line vue/multi-word-component-names
|
||
name: 'Profile',
|
||
setup() {
|
||
const authStore = useAuthStore()
|
||
const userStore = useUserStore()
|
||
return { authStore, userStore }
|
||
},
|
||
data() {
|
||
return {
|
||
authLoading: false,
|
||
statsLoading: false,
|
||
avatarLoadError: false,
|
||
personalBests: [],
|
||
upcomingEvents: [],
|
||
currentTrainingPlan: null
|
||
}
|
||
},
|
||
computed: {
|
||
user() {
|
||
return this.authStore.user
|
||
},
|
||
userStats() {
|
||
return this.userStore.userStats
|
||
},
|
||
userAchievements() {
|
||
return this.userStore.userAchievements
|
||
},
|
||
completedAchievements() {
|
||
return this.userStore.completedAchievements
|
||
},
|
||
recentAchievements() {
|
||
return this.userAchievements
|
||
.filter(a => a.verified)
|
||
.sort((a, b) => new Date(b.date) - new Date(a.date))
|
||
.slice(0, 3)
|
||
},
|
||
achievementProgress() {
|
||
return this.userStore.achievementProgress
|
||
},
|
||
statsError() {
|
||
return this.userStore.error
|
||
},
|
||
avatarUrl() {
|
||
if (!this.user?.avatar) return null;
|
||
let filename = this.user.avatar.trim('/').split('/').pop();
|
||
const baseUrl = 'https://begushiybashkir.ru/api/v1/user/avatars/';
|
||
console.log(baseUrl)
|
||
return baseUrl + filename;
|
||
},
|
||
joinDate() {
|
||
if (!this.user?.created_at) {
|
||
return 'Неизвестно';
|
||
}
|
||
// Пробуем разные форматы даты
|
||
let date;
|
||
if (this.user.created_at.includes('T')) {
|
||
// ISO формат
|
||
date = new Date(this.user.created_at);
|
||
} else {
|
||
// Другие форматы
|
||
date = new Date(this.user.created_at.replace(' ', 'T'));
|
||
}
|
||
|
||
console.log('DEBUG - parsed date:', date);
|
||
|
||
if (isNaN(date.getTime())) {
|
||
// Если все еще ошибка, пробуем убрать микросекунды
|
||
const cleanDate = this.user.created_at.split('.')[0];
|
||
date = new Date(cleanDate);
|
||
|
||
if (isNaN(date.getTime())) {
|
||
return 'Неизвестно';
|
||
}
|
||
}
|
||
|
||
const month = date.toLocaleString('ru-RU', { month: 'long' });
|
||
const year = date.getFullYear();
|
||
|
||
// Склоняем месяц
|
||
const monthNames = {
|
||
'январь': 'января', 'февраль': 'февраля', 'март': 'марта',
|
||
'апрель': 'апреля', 'май': 'мая', 'июнь': 'июня',
|
||
'июль': 'июля', 'август': 'августа', 'сентябрь': 'сентября',
|
||
'октябрь': 'октября', 'ноябрь': 'ноября', 'декабрь': 'декабря'
|
||
};
|
||
|
||
const monthName = monthNames[month] || month;
|
||
|
||
return `${monthName} ${year}`;
|
||
},
|
||
experienceLabel() {
|
||
const experienceMap = {
|
||
'beginner': 'Начинающий (0-6 месяцев)',
|
||
'intermediate': 'Любитель (6-24 месяцев)',
|
||
'advanced': 'Опытный (2+ лет)',
|
||
'professional': 'Профессионал'
|
||
};
|
||
return experienceMap[this.user?.experience] || 'Не указан';
|
||
},
|
||
goalsLabel() {
|
||
const goalsMap = {
|
||
'health': 'Улучшить здоровье',
|
||
'weight': 'Сбросить вес',
|
||
'first5k': 'Пробежать первые 5 км',
|
||
'first10k': 'Пробежать первые 10 км',
|
||
'halfMarathon': 'Подготовиться к полумарафону',
|
||
'marathon': 'Подготовиться к марафону',
|
||
'improve': 'Улучшить результаты',
|
||
'social': 'Общение и компания'
|
||
};
|
||
return goalsMap[this.user?.goals] || 'Не указана';
|
||
}
|
||
},
|
||
methods: {
|
||
handleFirstInteraction() {
|
||
if (!this.hasInteracted) {
|
||
this.hasInteracted = true
|
||
this.showContent()
|
||
clearTimeout(this.autoShowTimeout)
|
||
}
|
||
},
|
||
showContent() {
|
||
this.isContentVisible = true
|
||
// Эмитим событие для показа хедера
|
||
this.$emit('show-header')
|
||
},
|
||
async onAvatarUpdated() {
|
||
this.avatarLoadError = false;
|
||
await this.authStore.fetchProfile();
|
||
},
|
||
|
||
handleAvatarError() {
|
||
console.error('Ошибка загрузки аватара:', this.avatarUrl);
|
||
this.avatarLoadError = true;
|
||
},
|
||
|
||
async loadUserData() {
|
||
this.authLoading = true;
|
||
this.avatarLoadError = false;
|
||
try {
|
||
await this.authStore.fetchProfile();
|
||
await this.loadExtendedData();
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки данных:', error);
|
||
} finally {
|
||
this.authLoading = false;
|
||
}
|
||
},
|
||
|
||
async loadExtendedData() {
|
||
this.statsLoading = true;
|
||
try {
|
||
const [statsResult, achievementsResult, bestsResult, eventsResult, planResult] = await Promise.all([
|
||
this.userStore.fetchUserStats(),
|
||
this.userStore.fetchUserAchievements(),
|
||
this.fetchPersonalBests(),
|
||
this.fetchUpcomingEvents(),
|
||
this.fetchCurrentTrainingPlan()
|
||
]);
|
||
|
||
if (!statsResult.success) console.error('Ошибка статистики:', statsResult.error);
|
||
if (!achievementsResult.success) console.error('Ошибка достижений:', achievementsResult.error);
|
||
if (!bestsResult.success) console.error('Ошибка рекордов:', bestsResult.error);
|
||
if (!eventsResult.success) console.error('Ошибка событий:', eventsResult.error);
|
||
if (!planResult.success) console.error('Ошибка плана:', planResult.error);
|
||
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки расширенных данных:', error);
|
||
} finally {
|
||
this.statsLoading = false;
|
||
}
|
||
},
|
||
|
||
async fetchPersonalBests() {
|
||
try {
|
||
const response = await this.userStore.fetchPersonalBests;
|
||
this.personalBests = response.data;
|
||
return { success: true };
|
||
} catch (error) {
|
||
return { success: false, error: error.message };
|
||
}
|
||
},
|
||
|
||
async fetchUpcomingEvents() {
|
||
try {
|
||
const response = await this.userStore.apiClient.get('/events/upcoming');
|
||
this.upcomingEvents = response.data;
|
||
return { success: true };
|
||
} catch (error) {
|
||
return { success: false, error: error.message };
|
||
}
|
||
},
|
||
|
||
async fetchCurrentTrainingPlan() {
|
||
try {
|
||
const response = await this.userStore.apiClient.get('/training-plans/current');
|
||
this.currentTrainingPlan = response.data;
|
||
return { success: true };
|
||
} catch (error) {
|
||
return { success: false, error: error.message };
|
||
}
|
||
},
|
||
|
||
formatTime(minutes) {
|
||
if (!minutes) return '0 мин';
|
||
const hours = Math.floor(minutes / 60);
|
||
const mins = minutes % 60;
|
||
return hours > 0 ? `${hours}ч ${mins}мин` : `${mins} мин`;
|
||
},
|
||
|
||
formatDate(dateString) {
|
||
return new Date(dateString).toLocaleDateString('ru-RU');
|
||
},
|
||
|
||
formatEventDate(dateString) {
|
||
return new Date(dateString).toLocaleDateString('ru-RU', {
|
||
day: 'numeric',
|
||
month: 'long'
|
||
});
|
||
},
|
||
|
||
getDistanceLabel(distanceType) {
|
||
const labels = {
|
||
'5k': '5 км',
|
||
'10k': '10 км',
|
||
'half_marathon': 'Полумарафон',
|
||
'marathon': 'Марафон',
|
||
'other': 'Другая дистанция'
|
||
};
|
||
return labels[distanceType] || distanceType;
|
||
},
|
||
|
||
getEventTypeLabel(eventType) {
|
||
const labels = {
|
||
'race': 'Забег',
|
||
'training': 'Тренировка',
|
||
'social': 'Встреча',
|
||
'workshop': 'Семинар'
|
||
};
|
||
return labels[eventType] || eventType;
|
||
},
|
||
|
||
async refreshStats() {
|
||
await this.loadExtendedData();
|
||
},
|
||
|
||
async handleLogout() {
|
||
await this.authStore.logout();
|
||
this.$router.push('/');
|
||
},
|
||
|
||
editProfile() {
|
||
this.$router.push('/profile/edit');
|
||
},
|
||
|
||
viewDetailedStats() {
|
||
this.$router.push('/stats/detailed');
|
||
}
|
||
},
|
||
async mounted() {
|
||
if (!this.user) {
|
||
await this.loadUserData();
|
||
} else {
|
||
await this.loadExtendedData();
|
||
}
|
||
window.addEventListener('scroll', this.handleFirstInteraction, { passive: true, once: true })
|
||
window.addEventListener('click', this.handleFirstInteraction, { once: true })
|
||
window.addEventListener('touchstart', this.handleFirstInteraction, { once: true })
|
||
this.autoShowTimeout = setTimeout(() => {
|
||
if (!this.hasInteracted) {
|
||
this.showContent()
|
||
}
|
||
}, 3000)
|
||
},
|
||
|
||
beforeUnmount() {
|
||
// Убираем обработчики при размонтировании
|
||
window.removeEventListener('scroll', this.handleFirstInteraction)
|
||
window.removeEventListener('click', this.handleFirstInteraction)
|
||
window.removeEventListener('touchstart', this.handleFirstInteraction)
|
||
clearTimeout(this.autoShowTimeout)
|
||
},
|
||
}
|
||
</script>
|
||
|
||
|
||
|
||
<style scoped>
|
||
/* Добавляем новые стили для дополнительных секций */
|
||
|
||
.personal-bests-section,
|
||
.events-section,
|
||
.training-plan-section {
|
||
background: white;
|
||
padding: 1.5rem;
|
||
border-radius: 15px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.bests-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 1rem;
|
||
margin: 1rem 0;
|
||
}
|
||
|
||
.best-card {
|
||
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
|
||
padding: 1rem;
|
||
border-radius: 10px;
|
||
text-align: center;
|
||
border: 2px solid #e9ecef;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.best-card:hover {
|
||
transform: translateY(-3px);
|
||
border-color: #2e8b57;
|
||
}
|
||
|
||
.best-type {
|
||
font-weight: bold;
|
||
color: #2e8b57;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.best-time {
|
||
font-size: 1.2rem;
|
||
font-weight: bold;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.best-date {
|
||
color: #666;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.best-event {
|
||
color: #555;
|
||
font-style: italic;
|
||
margin-top: 0.5rem;
|
||
}
|
||
|
||
.verified-badge {
|
||
background: #2e8b57;
|
||
color: white;
|
||
padding: 0.2rem 0.5rem;
|
||
border-radius: 12px;
|
||
font-size: 0.7rem;
|
||
margin-top: 0.5rem;
|
||
display: inline-block;
|
||
}
|
||
|
||
.events-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
margin: 1rem 0;
|
||
}
|
||
|
||
.event-card {
|
||
background: #f8f9fa;
|
||
padding: 1rem;
|
||
border-radius: 10px;
|
||
border-left: 4px solid #2e8b57;
|
||
position: relative;
|
||
}
|
||
|
||
.event-date {
|
||
font-weight: bold;
|
||
color: #2e8b57;
|
||
}
|
||
|
||
.event-title {
|
||
font-weight: 600;
|
||
margin: 0.25rem 0;
|
||
}
|
||
|
||
.event-location {
|
||
color: #666;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.event-distance {
|
||
background: #e9ecef;
|
||
padding: 0.2rem 0.5rem;
|
||
border-radius: 12px;
|
||
font-size: 0.8rem;
|
||
display: inline-block;
|
||
margin-top: 0.5rem;
|
||
}
|
||
|
||
.event-type {
|
||
position: absolute;
|
||
top: 1rem;
|
||
right: 1rem;
|
||
background: #6c757d;
|
||
color: white;
|
||
padding: 0.2rem 0.5rem;
|
||
border-radius: 12px;
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.event-type.race {
|
||
background: #dc3545;
|
||
}
|
||
|
||
.event-type.training {
|
||
background: #2e8b57;
|
||
}
|
||
|
||
.event-type.social {
|
||
background: #fd7e14;
|
||
}
|
||
|
||
.event-type.workshop {
|
||
background: #6f42c1;
|
||
}
|
||
|
||
.plan-card {
|
||
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
|
||
padding: 1.5rem;
|
||
border-radius: 12px;
|
||
margin: 1rem 0;
|
||
border: 2px solid #e9ecef;
|
||
}
|
||
|
||
.plan-title {
|
||
font-size: 1.2rem;
|
||
font-weight: bold;
|
||
margin-bottom: 1rem;
|
||
color: #333;
|
||
}
|
||
|
||
.plan-progress {
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.progress-info {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 0.5rem;
|
||
font-size: 0.9rem;
|
||
color: #666;
|
||
}
|
||
|
||
.plan-target {
|
||
background: #2e8b57;
|
||
color: white;
|
||
padding: 0.5rem 1rem;
|
||
border-radius: 20px;
|
||
display: inline-block;
|
||
margin-bottom: 1rem;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.recent-achievements {
|
||
margin: 1rem 0;
|
||
}
|
||
|
||
.achievements-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
margin: 0.5rem 0;
|
||
}
|
||
|
||
.achievement-badge {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.5rem;
|
||
background: #f8f9fa;
|
||
border-radius: 8px;
|
||
border-left: 3px solid #ffd700;
|
||
}
|
||
|
||
.achievement-icon {
|
||
font-size: 1.2rem;
|
||
}
|
||
|
||
.achievement-title {
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* Адаптивность */
|
||
@media (max-width: 768px) {
|
||
.bests-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.event-type {
|
||
position: static;
|
||
margin-top: 0.5rem;
|
||
display: inline-block;
|
||
}
|
||
}
|
||
|
||
.page {
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
padding: 2rem;
|
||
}
|
||
|
||
.profile-content {
|
||
margin-top: 2rem;
|
||
}
|
||
|
||
.profile-header {
|
||
text-align: center;
|
||
margin-bottom: 3rem;
|
||
padding: 2rem;
|
||
background: white;
|
||
border-radius: 15px;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.avatar-section {
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.avatar-preview {
|
||
position: relative;
|
||
width: 150px;
|
||
height: 150px;
|
||
margin: 0 auto 1rem;
|
||
border-radius: 50%;
|
||
overflow: hidden;
|
||
border: 4px solid #2e8b57;
|
||
background: linear-gradient(135deg, #f5f5f5, #e0e0e0);
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
isolation: isolate;
|
||
/* Новое свойство */
|
||
background-image: none !important;
|
||
box-shadow: 0.2rem 0.2rem 0.3rem rgb(155, 155, 155);
|
||
/* Явно убираем фоновое изображение */
|
||
}
|
||
|
||
.avatar-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
transition: transform 0.3s ease;
|
||
display: block;
|
||
box-shadow: 0.2rem 0.2rem 0.3rem rgb(155, 155, 155);
|
||
/* Убедитесь, что block */
|
||
}
|
||
|
||
.avatar-placeholder {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 4rem;
|
||
background: linear-gradient(135deg, #2e8b57, #3cb371);
|
||
color: white;
|
||
}
|
||
|
||
.avatar-image:hover {
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
|
||
.profile-header h2 {
|
||
margin: 1rem 0 0.5rem;
|
||
color: #333;
|
||
font-size: 1.8rem;
|
||
}
|
||
|
||
.user-email,
|
||
.user-phone {
|
||
color: #666;
|
||
margin: 0.25rem 0;
|
||
}
|
||
|
||
.profile-info,
|
||
.profile-stats,
|
||
.achievements-preview {
|
||
background: white;
|
||
padding: 1.5rem;
|
||
border-radius: 15px;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.info-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||
gap: 1rem;
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 0.75rem;
|
||
background: #f8f9fa;
|
||
border-radius: 8px;
|
||
border-left: 4px solid #2e8b57;
|
||
}
|
||
|
||
.info-item label {
|
||
font-weight: 600;
|
||
color: #555;
|
||
}
|
||
|
||
.info-value {
|
||
color: #333;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.role-badge {
|
||
background: #2e8b57;
|
||
color: white;
|
||
padding: 0.25rem 0.75rem;
|
||
border-radius: 20px;
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
.stats-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.btn-refresh {
|
||
background: none;
|
||
border: none;
|
||
font-size: 1.2rem;
|
||
cursor: pointer;
|
||
padding: 8px;
|
||
border-radius: 50%;
|
||
transition: all 0.3s ease;
|
||
background-color: #f8f9fa;
|
||
}
|
||
|
||
.btn-refresh:hover:not(:disabled) {
|
||
background-color: #2e8b57;
|
||
color: white;
|
||
transform: rotate(90deg);
|
||
}
|
||
|
||
.btn-refresh:disabled {
|
||
cursor: not-allowed;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||
gap: 1rem;
|
||
margin: 1.5rem 0;
|
||
}
|
||
|
||
.stat-card {
|
||
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
|
||
padding: 1.5rem 1rem;
|
||
border-radius: 12px;
|
||
text-align: center;
|
||
border: 2px solid #e9ecef;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.stat-card:hover {
|
||
transform: translateY(-5px);
|
||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||
border-color: #2e8b57;
|
||
}
|
||
|
||
.stat-card h4 {
|
||
margin: 0 0 0.5rem;
|
||
color: #555;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.stat-card p {
|
||
margin: 0;
|
||
font-size: 1.5rem;
|
||
font-weight: bold;
|
||
color: #2e8b57;
|
||
}
|
||
|
||
.achievements-preview {
|
||
text-align: center;
|
||
}
|
||
|
||
.achievements-progress {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
margin: 1rem 0;
|
||
justify-content: center;
|
||
}
|
||
|
||
.progress-bar {
|
||
flex: 1;
|
||
max-width: 300px;
|
||
height: 12px;
|
||
background-color: #e0e0e0;
|
||
border-radius: 6px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, #2e8b57, #3cb371);
|
||
transition: width 0.5s ease;
|
||
}
|
||
|
||
.achievements-count {
|
||
margin: 1rem 0;
|
||
color: #666;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.profile-actions {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
max-width: 300px;
|
||
margin: 2rem auto;
|
||
}
|
||
|
||
.btn {
|
||
padding: 0.75rem 1.5rem;
|
||
border: none;
|
||
border-radius: 8px;
|
||
background: #2e8b57;
|
||
color: white;
|
||
font-size: 1rem;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
text-decoration: none;
|
||
display: inline-block;
|
||
text-align: center;
|
||
}
|
||
|
||
.btn:hover:not(:disabled) {
|
||
background: #26734d;
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(46, 139, 87, 0.3);
|
||
}
|
||
|
||
.btn:disabled {
|
||
background-color: #ccc;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.btn-outline {
|
||
background: white;
|
||
color: #2e8b57;
|
||
border: 2px solid #2e8b57;
|
||
}
|
||
|
||
.btn-outline:hover {
|
||
background: #2e8b57;
|
||
color: white;
|
||
}
|
||
|
||
.btn-logout {
|
||
background-color: #dc3545;
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.btn-logout:hover:not(:disabled) {
|
||
background-color: #c82333;
|
||
}
|
||
|
||
.btn-secondary {
|
||
background-color: #6c757d;
|
||
margin-top: 2rem;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background-color: #545b62;
|
||
}
|
||
|
||
.error-message {
|
||
background-color: #fee;
|
||
color: #c33;
|
||
padding: 2rem;
|
||
border-radius: 8px;
|
||
text-align: center;
|
||
margin: 2rem 0;
|
||
border-left: 4px solid #c33;
|
||
}
|
||
|
||
.loading {
|
||
text-align: center;
|
||
padding: 2rem;
|
||
font-size: 1.1rem;
|
||
color: #666;
|
||
}
|
||
|
||
.link {
|
||
color: #2e8b57;
|
||
text-decoration: none;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.link:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
/* Адаптивность */
|
||
@media (max-width: 768px) {
|
||
.page {
|
||
padding: 1rem;
|
||
}
|
||
|
||
.info-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.stats-grid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
|
||
.profile-actions {
|
||
max-width: 100%;
|
||
}
|
||
|
||
.achievements-progress {
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.avatar-preview {
|
||
width: 120px;
|
||
height: 120px;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 480px) {
|
||
.stats-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.profile-header {
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
.profile-header h2 {
|
||
font-size: 1.5rem;
|
||
}
|
||
}
|
||
</style> |