start new bb run club project on vue3

This commit is contained in:
2025-09-30 06:25:55 +05:00
parent db66e5279b
commit e3f07504f2
32 changed files with 5675 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
max_line_length = 100
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+30
View File
@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
+6
View File
@@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"singleQuote": true,
"printWidth": 100
}
+8
View File
@@ -0,0 +1,8 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"esbenp.prettier-vscode"
]
}
+44
View File
@@ -0,0 +1,44 @@
# bbvue
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Recommended Browser Setup
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
- Firefox:
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```
### Lint with [ESLint](https://eslint.org/)
```sh
npm run lint
```
+26
View File
@@ -0,0 +1,26 @@
import { defineConfig, globalIgnores } from 'eslint/config'
import globals from 'globals'
import js from '@eslint/js'
import pluginVue from 'eslint-plugin-vue'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
export default defineConfig([
{
name: 'app/files-to-lint',
files: ['**/*.{js,mjs,jsx,vue}'],
},
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
{
languageOptions: {
globals: {
...globals.browser,
},
},
},
js.configs.recommended,
...pluginVue.configs['flat/essential'],
skipFormatting,
])
+13
View File
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
+8
View File
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
{
"name": "bbvue",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --fix",
"format": "prettier --write src/"
},
"dependencies": {
"pinia": "^3.0.3",
"vue": "^3.5.22",
"vue-router": "^4.5.1"
},
"devDependencies": {
"@eslint/js": "^9.33.0",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/eslint-config-prettier": "^10.2.0",
"eslint": "^9.33.0",
"eslint-plugin-vue": "~10.4.0",
"globals": "^16.3.0",
"prettier": "3.6.2",
"vite": "^7.1.7",
"vite-plugin-vue-devtools": "^8.0.2"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+148
View File
@@ -0,0 +1,148 @@
<template>
<div id="app">
<header class="app-header">
<div class="container">
<div class="logo">
<h1>🏃 Бегущий Башкир</h1>
</div>
<nav class="main-nav">
<router-link to="/" class="nav-link">Главная</router-link>
<router-link to="/about" class="nav-link">О нас</router-link>
<router-link to="/achievements" class="nav-link">Достижения</router-link>
<router-link to="/gallery" class="nav-link">Галерея</router-link>
<router-link to="/training" class="nav-link">Тренировки</router-link>
<router-link to="/news" class="nav-link">Новости</router-link>
<router-link to="/members" class="nav-link">Участники</router-link>
<router-link to="/reviews" class="nav-link">Отзывы</router-link>
<router-link to="/login" class="nav-link">Войти</router-link>
</nav>
</div>
</header>
<main class="main-content">
<router-view />
</main>
<footer class="app-footer">
<div class="container">
<p>© 2025 Беговой клуб "Бегущий Башкир". Все права защищены.</p>
</div>
</footer>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Arial', sans-serif;
line-height: 1.6;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.app-header {
background-color: #2e8b57;
color: white;
padding: 1rem 0;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.logo h1 {
font-size: 1.8rem;
margin-bottom: 0.5rem;
}
.main-nav {
display: flex;
gap: 1.5rem;
flex-wrap: wrap;
}
.nav-link {
color: white;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 4px;
transition: background-color 0.3s;
}
.nav-link:hover,
.nav-link.router-link-active {
background-color: rgba(255, 255, 255, 0.2);
}
.main-content {
min-height: calc(100vh - 140px);
padding: 2rem 0;
}
.app-footer {
background-color: #1a3e23;
color: white;
text-align: center;
padding: 1rem 0;
margin-top: 2rem;
}
.page {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.page h1 {
color: #2e8b57;
margin-bottom: 1rem;
font-size: 2.5rem;
}
.page p {
font-size: 1.1rem;
margin-bottom: 1.5rem;
color: #666;
}
.btn {
display: inline-block;
background-color: #ffd700;
color: #333;
padding: 12px 30px;
border-radius: 5px;
text-decoration: none;
font-weight: bold;
border: none;
cursor: pointer;
transition: background-color 0.3s;
margin: 0.5rem;
}
.btn:hover {
background-color: #e6c200;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #545b62;
}
</style>
@@ -0,0 +1 @@
@import './base.css';
+14
View File
@@ -0,0 +1,14 @@
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')
+67
View File
@@ -0,0 +1,67 @@
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue')
},
{
path: '/achievements',
name: 'Achievements',
component: () => import('../views/Achievements.vue')
},
{
path: '/gallery',
name: 'Gallery',
component: () => import('../views/Gallery.vue')
},
{
path: '/training',
name: 'Training',
component: () => import('../views/Training.vue')
},
{
path: '/news',
name: 'News',
component: () => import('../views/News.vue')
},
{
path: '/members',
name: 'Members',
component: () => import('../views/Members.vue')
},
{
path: '/reviews',
name: 'Reviews',
component: () => import('../views/Reviews.vue')
},
{
path: '/login',
name: 'Login',
component: () => import('../views/Login.vue')
},
{
path: '/profile',
name: 'Profile',
component: () => import('../views/Profile.vue')
},
{
path: '/register',
name: 'Register',
component: () => import('../views/Register.vue')
}
]
})
export default router
@@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
+26
View File
@@ -0,0 +1,26 @@
<template>
<div class="page">
<h1>👥 О нашем клубе</h1>
<p>Беговой клуб "Бегущий Башкир" - это сообщество единомышленников в Уфе</p>
<div class="content-section">
<h2>Наш тренер</h2>
<div class="coach-card">
<img src="https://via.placeholder.com/150/2e8b57/ffffff?text=ЗТ"
alt="Тренер"
style="border-radius: 50%; margin: 1rem 0;">
<h3>Аминев Загир</h3>
<p>Мастер спорта по полиатлону, КМС по скайраннингу</p>
</div>
</div>
<router-link to="/training" class="btn">Посмотреть расписание</router-link>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'About'
}
</script>
@@ -0,0 +1,15 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>
@@ -0,0 +1,51 @@
<template>
<div class="page">
<h1>🏆 Наши достижения</h1>
<p>Лучшие результаты наших участников</p>
<div class="achievements-grid">
<div class="achievement-card">
<h3>🥇 Марафон 42.2 км</h3>
<p>Сергей - 3:27.49</p>
<p>Ғаяз - 3:34.33</p>
</div>
<div class="achievement-card">
<h3>🥈 Полумарафон 21.1 км</h3>
<p>Ильгам - 1:23.33</p>
<p>Данил - 1:30.40</p>
</div>
<div class="achievement-card">
<h3>🥉 Командные достижения</h3>
<p>III место в эстафете 4×400м (2024, 2025)</p>
</div>
</div>
<button class="btn" @click="$router.push('/members')">Посмотреть всех участников</button>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Achievements'
}
</script>
<style scoped>
.achievements-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin: 2rem 0;
}
.achievement-card {
background: white;
padding: 1.5rem;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
border-left: 4px solid #ffd700;
}
</style>
@@ -0,0 +1,47 @@
<template>
<div class="page">
<h1>📸 Галерея</h1>
<p>Моменты из жизни нашего клуба</p>
<div class="gallery-grid">
<div v-for="n in 6" :key="n" class="gallery-item">
<img :src="`https://via.placeholder.com/300x200/2e8b57/ffffff?text=Фото+${n}`"
alt="Галерея"
style="width: 100%; border-radius: 8px;">
</div>
</div>
<div class="gallery-actions">
<button class="btn">📁 Альбомы</button>
<button class="btn">🎥 Видео</button>
</div>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Gallery'
}
</script>
<style scoped>
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
margin: 2rem 0;
}
.gallery-item {
transition: transform 0.3s;
}
.gallery-item:hover {
transform: scale(1.05);
}
.gallery-actions {
margin-top: 2rem;
}
</style>
+56
View File
@@ -0,0 +1,56 @@
<template>
<div class="page home-page">
<h1>🏃 Добро пожаловать в Бегущий Башкир!</h1>
<p>Присоединяйтесь к нашему беговому сообществу в Уфе</p>
<div class="hero-section">
<img src="https://via.placeholder.com/800x400/2e8b57/ffffff?text=Бегущий+Башкир"
alt="Беговой клуб"
style="width: 100%; border-radius: 10px; margin: 2rem 0;">
</div>
<div class="quick-links">
<h2>Быстрые ссылки</h2>
<div class="links-grid">
<router-link to="/training" class="btn">📅 Расписание тренировок</router-link>
<router-link to="/achievements" class="btn">🏆 Наши достижения</router-link>
<router-link to="/members" class="btn">👥 Участники клуба</router-link>
<router-link to="/gallery" class="btn">📸 Галерея</router-link>
</div>
</div>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Home'
}
</script>
<style scoped>
.home-page {
text-align: center;
}
.hero-section {
margin: 2rem 0;
}
.links-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-top: 2rem;
}
.links-grid .btn {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 1rem;
text-decoration: none;
}
</style>
@@ -0,0 +1,9 @@
<script setup>
import TheWelcome from '../components/TheWelcome.vue'
</script>
<template>
<main>
<TheWelcome />
</main>
</template>
+64
View File
@@ -0,0 +1,64 @@
<template>
<div class="page">
<h1>🔐 Вход в систему</h1>
<p>Войдите в свой личный кабинет</p>
<div class="login-form">
<div class="form-group">
<input type="email" placeholder="Email" class="form-input">
</div>
<div class="form-group">
<input type="password" placeholder="Пароль" class="form-input">
</div>
<button class="btn" @click="$router.push('/profile')">Войти</button>
</div>
<div class="login-links">
<div class="register-link">
<p>Нет аккаунта? <router-link to="/register" class="link">Зарегистрируйтесь здесь</router-link></p>
</div>
<p><a href="#">Забыли пароль?</a></p>
</div>
<button class="btn btn-secondary" @click="$router.push('/')"> На главную</button>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Login'
}
</script>
<style scoped>
.login-form {
max-width: 300px;
margin: 2rem auto;
}
.form-group {
margin-bottom: 1rem;
}
.form-input {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 1rem;
}
.login-links {
margin-top: 1.5rem;
}
.login-links a {
color: #2e8b57;
text-decoration: none;
}
.login-links a:hover {
text-decoration: underline;
}
</style>
@@ -0,0 +1,69 @@
<template>
<div class="page">
<h1>👥 Участники клуба</h1>
<p>Наша дружная беговая семья</p>
<div class="members-grid">
<div v-for="member in members" :key="member.id" class="member-card">
<img :src="member.avatar"
:alt="member.name"
style="width: 80px; height: 80px; border-radius: 50%; margin-bottom: 1rem;">
<h3>{{ member.name }}</h3>
<p class="member-role">{{ member.role }}</p>
<p class="member-achievement">{{ member.achievement }}</p>
</div>
</div>
<button class="btn" @click="$router.push('/achievements')">🏆 Посмотреть достижения</button>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Members',
data() {
return {
members: [
{ id: 1, name: 'Сергей', role: 'Марафонец', achievement: 'Лучший: 3:27.49', avatar: 'https://via.placeholder.com/80/2e8b57/ffffff?text=С' },
{ id: 2, name: 'Ильгам', role: 'Спринтер', achievement: '10км: 37:59', avatar: 'https://via.placeholder.com/80/2e8b57/ffffff?text=И' },
{ id: 3, name: 'Данил', role: 'Ультрамарафонец', achievement: 'Трейлы 120км', avatar: 'https://via.placeholder.com/80/2e8b57/ffffff?text=Д' },
{ id: 4, name: 'Ғаяз', role: 'Стайер', achievement: 'Марафон: 3:34.33', avatar: 'https://via.placeholder.com/80/2e8b57/ffffff?text=Ғ' }
]
}
}
}
</script>
<style scoped>
.members-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
margin: 2rem 0;
}
.member-card {
background: white;
padding: 1.5rem;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
text-align: center;
transition: transform 0.3s;
}
.member-card:hover {
transform: translateY(-5px);
}
.member-role {
color: #2e8b57;
font-weight: bold;
margin: 0.5rem 0;
}
.member-achievement {
font-size: 0.9rem;
color: #666;
}
</style>
+55
View File
@@ -0,0 +1,55 @@
<template>
<div class="page">
<h1>📰 Новости клуба</h1>
<p>Самые свежие события и анонсы</p>
<div class="news-list">
<div v-for="item in news" :key="item.id" class="news-card">
<h3>{{ item.title }}</h3>
<p class="news-date">{{ item.date }}</p>
<p>{{ item.excerpt }}</p>
<button class="btn btn-secondary">Читать далее</button>
</div>
</div>
<button class="btn">📧 Подписаться на новости</button>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'News',
data() {
return {
news: [
{ id: 1, title: 'Новый набор в группу', date: '15.01.2025', excerpt: 'Открыт набор в группу для начинающих бегунов...' },
{ id: 2, title: 'Подготовка к весеннему марафону', date: '10.01.2025', excerpt: 'Стартует программа подготовки к Уфимскому марафону...' },
{ id: 3, title: 'Итоги забега РосХим', date: '05.01.2025', excerpt: 'Наши участники показали отличные результаты...' }
]
}
}
}
</script>
<style scoped>
.news-list {
max-width: 600px;
margin: 2rem auto;
}
.news-card {
background: white;
padding: 1.5rem;
margin: 1rem 0;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
text-align: left;
}
.news-date {
color: #666;
font-size: 0.9rem;
margin-bottom: 1rem;
}
</style>
@@ -0,0 +1,76 @@
<template>
<div class="page">
<h1>👤 Личный кабинет</h1>
<div class="profile-header">
<img src="https://via.placeholder.com/100/2e8b57/ffffff?text=У"
alt="Аватар"
style="width: 100px; height: 100px; border-radius: 50%;">
<h2>Иван Иванов</h2>
<p>Участник с января 2024</p>
</div>
<div class="profile-stats">
<h3>Моя статистика</h3>
<div class="stats-grid">
<div class="stat-card">
<h4>🏃 Всего пробег</h4>
<p>245 км</p>
</div>
<div class="stat-card">
<h4> Лучший результат</h4>
<p>10км - 48:15</p>
</div>
<div class="stat-card">
<h4>📅 Тренировок</h4>
<p>36</p>
</div>
</div>
</div>
<div class="profile-actions">
<button class="btn"> Редактировать профиль</button>
<button class="btn">📊 Подробная статистика</button>
<button class="btn" @click="$router.push('/training')">📅 Мой план тренировок</button>
</div>
<button class="btn btn-secondary" @click="$router.push('/')"> На главную</button>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Profile'
}
</script>
<style scoped>
.profile-header {
text-align: center;
margin-bottom: 2rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
margin: 1.5rem 0;
}
.stat-card {
background: white;
padding: 1rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
text-align: center;
}
.profile-actions {
display: flex;
flex-direction: column;
gap: 1rem;
max-width: 300px;
margin: 2rem auto;
}
</style>
@@ -0,0 +1,402 @@
<template>
<div class="page">
<div class="register-container">
<h1>👤 Регистрация</h1>
<p>Присоединяйтесь к нашему беговому сообществу</p>
<form @submit.prevent="handleRegister" class="register-form">
<div class="form-row">
<div class="form-group">
<label for="firstName">Имя *</label>
<input
id="firstName"
v-model="formData.firstName"
type="text"
class="form-input"
placeholder="Введите ваше имя"
required
>
</div>
<div class="form-group">
<label for="lastName">Фамилия *</label>
<input
id="lastName"
v-model="formData.lastName"
type="text"
class="form-input"
placeholder="Введите вашу фамилию"
required
>
</div>
</div>
<div class="form-group">
<label for="email">Email *</label>
<input
id="email"
v-model="formData.email"
type="email"
class="form-input"
placeholder="example@mail.ru"
required
>
</div>
<div class="form-group">
<label for="phone">Телефон</label>
<input
id="phone"
v-model="formData.phone"
type="tel"
class="form-input"
placeholder="+7 (999) 123-45-67"
>
</div>
<div class="form-row">
<div class="form-group">
<label for="password">Пароль *</label>
<input
id="password"
v-model="formData.password"
type="password"
class="form-input"
placeholder="Не менее 6 символов"
required
minlength="6"
>
</div>
<div class="form-group">
<label for="confirmPassword">Подтверждение пароля *</label>
<input
id="confirmPassword"
v-model="formData.confirmPassword"
type="password"
class="form-input"
placeholder="Повторите пароль"
required
>
</div>
</div>
<div class="form-group">
<label for="experience">Уровень подготовки</label>
<select
id="experience"
v-model="formData.experience"
class="form-input"
>
<option value="">Выберите уровень</option>
<option value="beginner">Начинающий (0-6 месяцев)</option>
<option value="intermediate">Любитель (6-24 месяцев)</option>
<option value="advanced">Опытный (2+ лет)</option>
<option value="professional">Профессионал</option>
</select>
</div>
<div class="form-group">
<label for="goals">Цели</label>
<select
id="goals"
v-model="formData.goals"
class="form-input"
>
<option value="">Выберите цель</option>
<option value="health">Улучшить здоровье</option>
<option value="weight">Сбросить вес</option>
<option value="first5k">Пробежать первые 5 км</option>
<option value="first10k">Пробежать первые 10 км</option>
<option value="halfMarathon">Подготовиться к полумарафону</option>
<option value="marathon">Подготовиться к марафону</option>
<option value="improve">Улучшить результаты</option>
<option value="social">Общение и компания</option>
</select>
</div>
<div class="form-group checkbox-group">
<label class="checkbox-label">
<input
v-model="formData.agreeTerms"
type="checkbox"
class="checkbox"
required
>
<span class="checkmark"></span>
Я соглашаюсь с
<a href="#" class="link">правилами клуба</a> и
<a href="#" class="link">политикой конфиденциальности</a> *
</label>
</div>
<div class="form-group checkbox-group">
<label class="checkbox-label">
<input
v-model="formData.newsletter"
type="checkbox"
class="checkbox"
>
<span class="checkmark"></span>
Хочу получать новости о тренировках и мероприятиях
</label>
</div>
<button
type="submit"
class="btn btn-primary"
:disabled="!formData.agreeTerms || loading"
>
{{ loading ? 'Регистрация...' : '🏃 Зарегистрироваться' }}
</button>
<div v-if="error" class="error-message">
{{ error }}
</div>
</form>
<div class="login-link">
<p>Уже есть аккаунт? <router-link to="/login" class="link">Войдите здесь</router-link></p>
</div>
<div class="benefits">
<h3>Что вы получите после регистрации:</h3>
<ul class="benefits-list">
<li> Доступ к расписанию тренировок</li>
<li> Персональный трекер прогресса</li>
<li> Общение с тренером и участниками</li>
<li> Участие в клубных мероприятиях</li>
<li> Скидки на стартовые взносы</li>
</ul>
</div>
</div>
<button class="btn btn-secondary" @click="$router.push('/')"> На главную</button>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Register',
data() {
return {
formData: {
firstName: '',
lastName: '',
email: '',
phone: '',
password: '',
confirmPassword: '',
experience: '',
goals: '',
agreeTerms: false,
newsletter: true
},
loading: false,
error: ''
}
},
methods: {
async handleRegister() {
// Валидация
if (this.formData.password !== this.formData.confirmPassword) {
this.error = 'Пароли не совпадают'
return
}
if (this.formData.password.length < 6) {
this.error = 'Пароль должен содержать не менее 6 символов'
return
}
if (!this.formData.agreeTerms) {
this.error = 'Необходимо согласие с правилами клуба'
return
}
this.loading = true
this.error = ''
try {
// Здесь будет API call к backend
console.log('Регистрация:', this.formData)
// Имитация задержки сети
await new Promise(resolve => setTimeout(resolve, 1500))
// Успешная регистрация
this.$router.push('/login?message=registered')
} catch (err) {
this.error = 'Ошибка регистрации. Попробуйте еще раз.'
} finally {
this.loading = false
}
}
}
}
</script>
<style scoped>
.register-container {
max-width: 500px;
margin: 0 auto;
text-align: left;
}
.register-form {
background: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 2px 15px rgba(0,0,0,0.1);
margin: 2rem 0;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #333;
}
.form-input {
width: 100%;
padding: 12px;
border: 2px solid #e1e5e9;
border-radius: 6px;
font-size: 1rem;
transition: border-color 0.3s;
}
.form-input:focus {
outline: none;
border-color: #2e8b57;
}
.checkbox-group {
margin: 1.5rem 0;
}
.checkbox-label {
display: flex;
align-items: flex-start;
cursor: pointer;
font-weight: normal;
}
.checkbox {
margin-right: 10px;
margin-top: 3px;
}
.checkmark {
width: 18px;
height: 18px;
border: 2px solid #ddd;
border-radius: 3px;
margin-right: 10px;
margin-top: 2px;
position: relative;
flex-shrink: 0;
}
.checkbox:checked + .checkmark {
background-color: #2e8b57;
border-color: #2e8b57;
}
.checkbox:checked + .checkmark::after {
content: '✓';
color: white;
position: absolute;
top: -2px;
left: 2px;
font-size: 14px;
}
.btn-primary {
width: 100%;
background-color: #2e8b57;
color: white;
padding: 15px;
font-size: 1.1rem;
margin-top: 1rem;
}
.btn-primary:hover:not(:disabled) {
background-color: #26734a;
}
.btn-primary:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.error-message {
background-color: #fee;
color: #c33;
padding: 12px;
border-radius: 6px;
margin-top: 1rem;
border-left: 4px solid #c33;
}
.login-link {
text-align: center;
margin: 1.5rem 0;
}
.link {
color: #2e8b57;
text-decoration: none;
}
.link:hover {
text-decoration: underline;
}
.benefits {
background-color: #f8fff8;
padding: 1.5rem;
border-radius: 8px;
border-left: 4px solid #2e8b57;
margin: 2rem 0;
}
.benefits h3 {
color: #2e8b57;
margin-bottom: 1rem;
}
.benefits-list {
list-style: none;
padding: 0;
}
.benefits-list li {
padding: 0.3rem 0;
color: #555;
}
/* Адаптивность */
@media (max-width: 600px) {
.form-row {
grid-template-columns: 1fr;
}
.register-form {
padding: 1.5rem;
}
}
</style>
@@ -0,0 +1,85 @@
<template>
<div class="page">
<h1> Отзывы</h1>
<p>Что говорят наши участники</p>
<div class="reviews-list">
<div v-for="review in reviews" :key="review.id" class="review-card">
<div class="review-header">
<span class="review-author">{{ review.author }}</span>
<span class="review-rating"> {{ review.rating }}/5</span>
</div>
<p class="review-text">{{ review.text }}</p>
<p class="review-date">{{ review.date }}</p>
</div>
</div>
<div class="review-actions">
<button class="btn" @click="$router.push('/login')"> Оставить отзыв</button>
<button class="btn btn-secondary">📊 Все отзывы</button>
</div>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Reviews',
data() {
return {
reviews: [
{ id: 1, author: 'Анна', rating: 5, text: 'Отличный клуб! За 3 месяца улучшила результат на 10км на 8 минут!', date: '15.01.2025' },
{ id: 2, author: 'Михаил', rating: 5, text: 'Профессиональный тренер и дружеская атмосфера. Рекомендую!', date: '12.01.2025' },
{ id: 3, author: 'Елена', rating: 4, text: 'Очень нравятся групповые тренировки, всегда есть с кем побегать', date: '10.01.2025' }
]
}
}
}
</script>
<style scoped>
.reviews-list {
max-width: 600px;
margin: 2rem auto;
}
.review-card {
background: white;
padding: 1.5rem;
margin: 1rem 0;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
text-align: left;
}
.review-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.review-author {
font-weight: bold;
color: #2e8b57;
}
.review-rating {
color: #ffd700;
font-weight: bold;
}
.review-text {
margin-bottom: 1rem;
line-height: 1.5;
}
.review-date {
color: #666;
font-size: 0.9rem;
}
.review-actions {
margin-top: 2rem;
}
</style>
@@ -0,0 +1,68 @@
<template>
<div class="page">
<h1>📅 Тренировки</h1>
<p>Расписание и программы тренировок</p>
<div class="schedule-section">
<h2>Расписание на неделю</h2>
<div class="schedule-grid">
<div v-for="day in schedule" :key="day.name" class="schedule-day">
<h4>{{ day.name }}</h4>
<p><strong>{{ day.time }}</strong></p>
<p>{{ day.activity }}</p>
</div>
</div>
</div>
<div class="training-actions">
<button class="btn">📚 База знаний</button>
<button class="btn">💪 Программы тренировок</button>
<button class="btn" @click="$router.push('/members')">👥 Найти партнера для тренировок</button>
</div>
</div>
</template>
<script>
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: 'Training',
data() {
return {
schedule: [
{ name: 'Понедельник', time: '19:30', activity: 'Техника бега + ОФП' },
{ name: 'Вторник', time: '--:--', activity: 'Восстановление' },
{ name: 'Среда', time: '19:30', activity: 'Техника бега + СБУ' },
{ name: 'Четверг', time: '--:--', activity: 'Восстановление' },
{ name: 'Пятница', time: '--:--', activity: 'Восстановление' },
{ name: 'Суббота', time: '10:00', activity: 'Длительный кросс' },
{ name: 'Воскресенье', time: '--:--', activity: 'Восстановление' }
]
}
}
}
</script>
<style scoped>
.schedule-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
margin: 2rem 0;
}
.schedule-day {
background: white;
padding: 1rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
text-align: center;
}
.training-actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
margin-top: 2rem;
}
</style>
+22
View File
@@ -0,0 +1,22 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
server: {
port: 3001,
host: true
}
})