modified: begushiybashkir/bbvue/src/router/index.js
modified: begushiybashkir/bbvue/src/views/Login.vue new file: begushiybashkir/bbvue/src/views/Logout.vue modified: begushiybashkir/bbvue/src/views/News.vue modified: begushiybashkir/bbvue/src/views/Profile.vue modified: serv_nginx/api_bb/cmd/main.go modified: serv_nginx/api_bb/go.mod modified: serv_nginx/api_bb/go.sum new file: serv_nginx/api_bb/internal/app/app.go new file: serv_nginx/api_bb/internal/database/database.go new file: serv_nginx/api_bb/internal/database/migrate.go new file: serv_nginx/api_bb/internal/handlers/news_handler.go new file: serv_nginx/api_bb/internal/models/news.go new file: serv_nginx/api_bb/internal/repository/comment_repository.go new file: serv_nginx/api_bb/internal/repository/news_repository.go modified: serv_nginx/api_bb/internal/routes/routes.go new file: serv_nginx/api_bb/internal/service/news_service.go modified: serv_nginx/api_bb/pkg/utils/utils.go save router paths to login logout profile from upsunction commit
This commit is contained in:
@@ -48,7 +48,8 @@ const router = createRouter({
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('../views/Login.vue')
|
||||
component: () => import('../views/Login.vue'),
|
||||
meta: { guestOnly: true }
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
@@ -59,7 +60,8 @@ const router = createRouter({
|
||||
{
|
||||
path: '/register',
|
||||
name: 'Register',
|
||||
component: () => import('../views/Register.vue')
|
||||
component: () => import('../views/Register.vue'),
|
||||
meta: { guestOnly: true }
|
||||
},
|
||||
{
|
||||
path: '/profile/edit',
|
||||
@@ -76,33 +78,83 @@ const router = createRouter({
|
||||
path: '/privacy',
|
||||
name: 'PrivacyPolicy',
|
||||
component: () => import('../views/PrivacyPolicy.vue')
|
||||
},
|
||||
// Добавляем маршрут для выхода
|
||||
{
|
||||
path: '/logout',
|
||||
name: 'Logout',
|
||||
component: () => import('../views/Logout.vue')
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Функция для показа уведомлений
|
||||
function showNotification(message) {
|
||||
// Создаем элемент уведомления
|
||||
const notification = document.createElement('div')
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #2e8b57;
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 10000;
|
||||
max-width: 300px;
|
||||
font-family: Arial, sans-serif;
|
||||
`
|
||||
notification.textContent = message
|
||||
|
||||
document.body.appendChild(notification)
|
||||
|
||||
// Автоматически удаляем через 3 секунды
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification)
|
||||
}
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// Если пользователь переходит на защищенные страницы и не авторизован
|
||||
|
||||
// Проверяем, требует ли маршрут аутентификации
|
||||
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
|
||||
// Проверяем, есть ли токен в localStorage
|
||||
// Если есть токен, пробуем загрузить профиль
|
||||
if (authStore.token) {
|
||||
try {
|
||||
// Пытаемся загрузить профиль
|
||||
await authStore.fetchProfile()
|
||||
next()
|
||||
return
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
console.log('Token validation failed:', error)
|
||||
// Если токен невалидный, очищаем его и редиректим на логин
|
||||
authStore.clearAuth()
|
||||
next('/login')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Если нет токена, редиректим на логин
|
||||
next('/login')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
|
||||
// Проверяем, предназначен ли маршрут только для гостей
|
||||
if (to.meta.guestOnly && authStore.isAuthenticated) {
|
||||
showNotification("Вы уже авторизованы. Перенаправляем в профиль...")
|
||||
|
||||
// Ждем немного чтобы пользователь увидел уведомление, затем редиректим
|
||||
setTimeout(() => {
|
||||
next('/profile')
|
||||
}, 2000)
|
||||
return
|
||||
}
|
||||
|
||||
// Если все проверки пройдены, разрешаем навигацию
|
||||
next()
|
||||
})
|
||||
|
||||
|
||||
|
||||
export default router
|
||||
export default router
|
||||
@@ -62,11 +62,74 @@ export default {
|
||||
methods: {
|
||||
async handleLogin() {
|
||||
const result = await this.authStore.login(this.credentials)
|
||||
alert("register success" + result.success + "| data: " + result.data)
|
||||
|
||||
if (result.success) {
|
||||
this.$router.push('/profile')
|
||||
// Показываем уведомление об успешном входе
|
||||
this.showSuccessNotification()
|
||||
|
||||
// Редиректим после небольшой задержки
|
||||
setTimeout(() => {
|
||||
this.$router.push('/profile')
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
showSuccessNotification() {
|
||||
const notification = document.createElement('div')
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #2e8b57;
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 10000;
|
||||
max-width: 300px;
|
||||
font-family: Arial, sans-serif;
|
||||
`
|
||||
notification.textContent = '✅ Вход выполнен успешно!'
|
||||
|
||||
document.body.appendChild(notification)
|
||||
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification)
|
||||
}
|
||||
}, 3000)
|
||||
}
|
||||
},
|
||||
// Добавляем проверку при монтировании компонента
|
||||
mounted() {
|
||||
// Если пользователь уже авторизован, показываем уведомление
|
||||
if (this.authStore.isAuthenticated) {
|
||||
this.showAlreadyLoggedInNotification()
|
||||
}
|
||||
},
|
||||
showAlreadyLoggedInNotification() {
|
||||
const notification = document.createElement('div')
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #ffd700;
|
||||
color: #333;
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 10000;
|
||||
max-width: 300px;
|
||||
font-family: Arial, sans-serif;
|
||||
`
|
||||
notification.textContent = 'ℹ️ Вы уже авторизованы!'
|
||||
|
||||
document.body.appendChild(notification)
|
||||
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification)
|
||||
}
|
||||
}, 3000)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<h1>🚪 Выход из системы</h1>
|
||||
<div class="logout-content">
|
||||
<p>Выполняется выход из системы...</p>
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
|
||||
export default {
|
||||
// eslint-disable-next-line vue/multi-word-component-names
|
||||
name: 'Logout',
|
||||
setup() {
|
||||
const authStore = useAuthStore()
|
||||
return { authStore }
|
||||
},
|
||||
async mounted() {
|
||||
// Выполняем выход
|
||||
await this.authStore.logout()
|
||||
|
||||
// Редиректим на главную страницу
|
||||
this.$router.push('/')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.logout-content {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #2e8b57;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
@@ -221,182 +221,13 @@ export default {
|
||||
showNewsModal: false,
|
||||
selectedNews: null,
|
||||
subscribeEmail: '',
|
||||
news: [], // Теперь пустой массив
|
||||
filters: [
|
||||
{ value: 'all', label: 'Все новости' },
|
||||
{ value: 'events', label: 'События' },
|
||||
{ value: 'training', label: 'Тренировки' },
|
||||
{ value: 'achievements', label: 'Достижения' },
|
||||
{ value: 'community', label: 'Сообщество' }
|
||||
],
|
||||
news: [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Открыт набор в новую группу для начинающих',
|
||||
excerpt: 'Приглашаем всех желающих начать свой беговой путь. Бесплатное пробное занятие и индивидуальный подход к каждому.',
|
||||
date: '2025-01-15',
|
||||
category: 'training',
|
||||
image: 'news1.jpg',
|
||||
views: 124,
|
||||
comments: 8,
|
||||
content: `
|
||||
<p>Дорогие друзья! Мы рады сообщить об открытии новой группы для начинающих бегунов. Если вы всегда хотели начать бегать, но не знали как — это ваш шанс!</p>
|
||||
|
||||
<h3>Что вас ждет:</h3>
|
||||
<ul>
|
||||
<li>✅ Бесплатная первая тренировка</li>
|
||||
<li>✅ Постепенное увеличение нагрузок</li>
|
||||
<li>✅ Работа над правильной техникой</li>
|
||||
<li>✅ Поддержка опытных участников</li>
|
||||
</ul>
|
||||
|
||||
<p><strong>Первая тренировка:</strong> 20 января в 19:30 в Парке Якутова</p>
|
||||
<p>Не упустите возможность начать свой беговой путь в дружеской атмосфере нашего клуба!</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Стартует программа подготовки к Уфимскому марафону',
|
||||
excerpt: '16-недельная программа подготовки для тех, кто хочет успешно выступить на главном беговом событии весны.',
|
||||
date: '2025-01-10',
|
||||
category: 'events',
|
||||
image: 'news2.jpg',
|
||||
views: 89,
|
||||
comments: 12,
|
||||
content: `
|
||||
<p>Внимание всем бегунам! Открывается запись на специальную программу подготовки к Уфимскому марафону 2025.</p>
|
||||
|
||||
<h3>Детали программы:</h3>
|
||||
<ul>
|
||||
<li>📅 Продолжительность: 16 недель</li>
|
||||
<li>🏃♂️ Тренировки: 3-4 раза в неделю</li>
|
||||
<li>🎯 Дистанции: от 5км до марафона</li>
|
||||
<li>👨🏫 Индивидуальные планы от тренера</li>
|
||||
</ul>
|
||||
|
||||
<p>Программа включает в себя все аспекты подготовки: беговые объемы, силовую подготовку, питание и восстановление.</p>
|
||||
|
||||
<p><strong>Старт программы:</strong> 1 февраля 2025</p>
|
||||
<p><strong>Место:</strong> Основные тренировки в Парке Якутова и на стадионе Динамо</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Итоги забега РосХим Стерлитамак 2025',
|
||||
excerpt: 'Наши участники показали блестящие результаты на зимнем забеге. Поздравляем всех финишеров и призеров!',
|
||||
date: '2025-01-05',
|
||||
category: 'achievements',
|
||||
image: 'news3.jpg',
|
||||
views: 156,
|
||||
comments: 15,
|
||||
content: `
|
||||
<p>Гордимся нашими бегунами! На прошедшем забеге РосХим в Стерлитамаке участники клуба "Бегущий Башкир" показали отличные результаты.</p>
|
||||
|
||||
<h3>Лучшие результаты:</h3>
|
||||
<ul>
|
||||
<li>🥇 Сергей — 1 место в возрастной категории (10км - 36:52)</li>
|
||||
<li>🥈 Ильгам — 2 место (10км - 37:59)</li>
|
||||
<li>🥉 Данил — 3 место (21км - 1:30:40)</li>
|
||||
</ul>
|
||||
|
||||
<p>Всего от нашего клуба в забеге участвовало 12 человек, и каждый показал достойный результат!</p>
|
||||
|
||||
<p>Особые поздравления нашим новичкам, которые впервые преодолели дистанцию 10 км. Вы большие молодцы!</p>
|
||||
|
||||
<p>Следующий старт — Уфимский полумарафон 15 февраля. Готовимся!</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: 'Зимний спортивный фестиваль от клуба',
|
||||
excerpt: 'Приглашаем всех на зимний фестиваль бега с мастер-классами, эстафетами и горячим чаем.',
|
||||
date: '2024-12-28',
|
||||
category: 'community',
|
||||
image: 'news4.jpg',
|
||||
views: 78,
|
||||
comments: 6,
|
||||
content: `
|
||||
<p>Дорогие друзья! Приглашаем вас на наш традиционный зимний спортивный фестиваль.</p>
|
||||
|
||||
<h3>Программа мероприятия:</h3>
|
||||
<ul>
|
||||
<li>⏰ 10:00 — Регистрация участников</li>
|
||||
<li>🏃♂️ 10:30 — Эстафеты для всех возрастов</li>
|
||||
<li>🎯 11:30 — Мастер-класс по технике зимнего бега</li>
|
||||
<li>🍵 12:30 — Чаепитие с угощениями</li>
|
||||
<li>🎁 13:00 — Награждение и розыгрыш призов</li>
|
||||
</ul>
|
||||
|
||||
<p>Мероприятие бесплатное для всех участников клуба. Приглашаем также друзей и семьи!</p>
|
||||
|
||||
<p><strong>Когда:</strong> 15 января 2025</p>
|
||||
<p><strong>Где:</strong> Парк Якутова, главная аллея</p>
|
||||
|
||||
<p>Не забудьте теплую одежду и хорошее настроение!</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: 'Новые тренировочные программы от тренера',
|
||||
excerpt: 'Загир Аминев разработал новые программы тренировок для разных уровней подготовки.',
|
||||
date: '2024-12-20',
|
||||
category: 'training',
|
||||
image: 'news5.jpg',
|
||||
views: 92,
|
||||
comments: 3,
|
||||
content: `
|
||||
<p>Наш тренер Загир Аминев подготовил новые тренировочные программы, которые уже доступны для всех участников клуба.</p>
|
||||
|
||||
<h3>Что нового:</h3>
|
||||
<ul>
|
||||
<li>📊 Программа для начинающих (0-3 месяца)</li>
|
||||
<li>⚡ Программа для любителей (3-12 месяцев)</li>
|
||||
<li>🏆 Программа для продвинутых (1+ год)</li>
|
||||
<li>🏔️ Специальная программа для трейлраннинга</li>
|
||||
</ul>
|
||||
|
||||
<p>Каждая программа включает:</p>
|
||||
<ul>
|
||||
<li>✅ Подробное расписание тренировок</li>
|
||||
<li>✅ Рекомендации по питанию</li>
|
||||
<li>✅ План восстановления</li>
|
||||
<li>✅ Советы по экипировке</li>
|
||||
</ul>
|
||||
|
||||
<p>Получить программу можно у тренера на любой тренировке или написав в Telegram.</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: 'Набор волонтеров на Уфимский марафон',
|
||||
excerpt: 'Приглашаем желающих помочь в организации главного бегового события весны в Уфе.',
|
||||
date: '2024-12-15',
|
||||
category: 'community',
|
||||
image: 'news6.jpg',
|
||||
views: 64,
|
||||
comments: 4,
|
||||
content: `
|
||||
<p>Друзья! Организационный комитет Уфимского марафона начинает набор волонтеров, и мы приглашаем участников нашего клуба присоединиться.</p>
|
||||
|
||||
<h3>Чем могут помочь волонтеры:</h3>
|
||||
<ul>
|
||||
<li>📋 Регистрация участников</li>
|
||||
<li>🎽 Выдача стартовых пакетов</li>
|
||||
<li>💧 Организация пунктов питания</li>
|
||||
<li>🏁 Помощь на финише</li>
|
||||
<li>📢 Информационная поддержка</li>
|
||||
</ul>
|
||||
|
||||
<p>Все волонтеры получат:</p>
|
||||
<ul>
|
||||
<li>✅ Фирменную футболку волонтера</li>
|
||||
<li>✅ Питание в день мероприятия</li>
|
||||
<li>✅ Благодарственное письмо</li>
|
||||
<li>✅ Незабываемые эмоции</li>
|
||||
</ul>
|
||||
|
||||
<p>Если хотите стать часть команды волонтеров, пишите Загиру в Telegram.</p>
|
||||
`
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -417,6 +248,44 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchNews() {
|
||||
try {
|
||||
const response = await this.$http.get('/api/news', {
|
||||
params: {
|
||||
limit: 20,
|
||||
offset: 0
|
||||
}
|
||||
})
|
||||
this.news = response.data.news
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch news:', error)
|
||||
// Fallback на локальные данные если API недоступно
|
||||
this.news = this.getFallbackNews()
|
||||
}
|
||||
},
|
||||
async openNewsModal(newsItem) {
|
||||
try {
|
||||
// Получаем полную новость с API
|
||||
const response = await this.$http.get(`/api/news/${newsItem.id}`)
|
||||
this.selectedNews = response.data
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch news details:', error)
|
||||
this.selectedNews = newsItem
|
||||
}
|
||||
|
||||
this.showNewsModal = true
|
||||
document.body.style.overflow = 'hidden'
|
||||
},
|
||||
async handleSubscribe() {
|
||||
try {
|
||||
await this.$http.post('/api/subscribe', { email: this.subscribeEmail })
|
||||
alert('Спасибо за подписку! Проверьте вашу почту для подтверждения.')
|
||||
this.subscribeEmail = ''
|
||||
} catch (error) {
|
||||
console.error('Subscription failed:', error)
|
||||
alert('Ошибка подписки. Попробуйте позже.')
|
||||
}
|
||||
},
|
||||
setFilter(filter) {
|
||||
this.activeFilter = filter
|
||||
this.visibleNews = 6
|
||||
@@ -424,14 +293,6 @@ export default {
|
||||
loadMore() {
|
||||
this.visibleNews += 3
|
||||
},
|
||||
openNewsModal(newsItem) {
|
||||
this.selectedNews = newsItem
|
||||
this.showNewsModal = true
|
||||
document.body.style.overflow = 'hidden'
|
||||
|
||||
// Увеличиваем счетчик просмотров
|
||||
newsItem.views++
|
||||
},
|
||||
closeNewsModal() {
|
||||
this.showNewsModal = false
|
||||
document.body.style.overflow = ''
|
||||
@@ -449,16 +310,6 @@ export default {
|
||||
alert('Ссылка скопирована в буфер обмена!')
|
||||
}
|
||||
},
|
||||
handleSubscribe() {
|
||||
// Здесь будет логика подписки
|
||||
console.log('Подписка на email:', this.subscribeEmail)
|
||||
alert('Спасибо за подписку! Проверьте вашу почту для подтверждения.')
|
||||
this.subscribeEmail = ''
|
||||
},
|
||||
getImageUrl(imageName) {
|
||||
// Заглушка для изображений
|
||||
return `https://via.placeholder.com/400x250/2e8b57/ffffff?text=${encodeURIComponent(imageName)}`
|
||||
},
|
||||
getCategoryLabel(category) {
|
||||
const labels = {
|
||||
'events': 'События',
|
||||
|
||||
@@ -209,7 +209,7 @@ export default {
|
||||
},
|
||||
async handleLogout() {
|
||||
await this.authStore.logout()
|
||||
this.$router.push('/login')
|
||||
this.$router.push('/')
|
||||
},
|
||||
editProfile() {
|
||||
this.$router.push('/profile/edit')
|
||||
|
||||
Reference in New Issue
Block a user