refactor: перенёс достижения в профиль
This commit is contained in:
@@ -13,7 +13,6 @@ const navItems: NavItem[] = [
|
|||||||
{ label: 'Главная', icon: 'home', to: '/', roles: ['student'] },
|
{ label: 'Главная', icon: 'home', to: '/', roles: ['student'] },
|
||||||
{ label: 'Каталог', icon: 'books', to: '/catalog', roles: ['student'] },
|
{ label: 'Каталог', icon: 'books', to: '/catalog', roles: ['student'] },
|
||||||
{ label: 'Мои записи', icon: 'clipboard-list', to: '/my-lectures', roles: ['student'] },
|
{ label: 'Мои записи', icon: 'clipboard-list', to: '/my-lectures', roles: ['student'] },
|
||||||
{ label: 'Достижения', icon: 'trophy', to: '/achievements', roles: ['student'] },
|
|
||||||
{ label: 'Профиль', icon: 'user', to: '/profile', roles: ['student'] },
|
{ label: 'Профиль', icon: 'user', to: '/profile', roles: ['student'] },
|
||||||
// Teacher
|
// Teacher
|
||||||
{ label: 'Дашборд', icon: 'chart-bar', to: '/teacher', roles: ['teacher'] },
|
{ label: 'Дашборд', icon: 'chart-bar', to: '/teacher', roles: ['teacher'] },
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ const router = createRouter({
|
|||||||
{ path: '/my-lectures', name: 'my-lectures', component: () => import('@/views/student/MyLecturesView.vue'), meta: { role: 'student' } },
|
{ path: '/my-lectures', name: 'my-lectures', component: () => import('@/views/student/MyLecturesView.vue'), meta: { role: 'student' } },
|
||||||
{ path: '/review/:id', name: 'review-form', component: () => import('@/views/student/ReviewFormView.vue'), meta: { role: 'student' } },
|
{ path: '/review/:id', name: 'review-form', component: () => import('@/views/student/ReviewFormView.vue'), meta: { role: 'student' } },
|
||||||
{ path: '/profile', name: 'profile', component: () => import('@/views/student/ProfileView.vue') },
|
{ path: '/profile', name: 'profile', component: () => import('@/views/student/ProfileView.vue') },
|
||||||
{ path: '/achievements', name: 'achievements', component: () => import('@/views/student/AchievementsView.vue'), meta: { role: 'student' } },
|
|
||||||
{ path: '/notifications', name: 'notifications', component: () => import('@/views/student/NotificationsView.vue') },
|
{ path: '/notifications', name: 'notifications', component: () => import('@/views/student/NotificationsView.vue') },
|
||||||
|
|
||||||
// Teacher
|
// Teacher
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed, onMounted } from 'vue'
|
|
||||||
import { useAuthStore } from '@/stores/auth'
|
|
||||||
import { useUserStore } from '@/stores/user'
|
|
||||||
import AchievementBadge from '@/components/ui/AchievementBadge.vue'
|
|
||||||
import EmptyState from '@/components/ui/EmptyState.vue'
|
|
||||||
|
|
||||||
const userStore = useUserStore()
|
|
||||||
const auth = useAuthStore()
|
|
||||||
const unlocked = computed(() => userStore.achievements.filter(a => a.unlocked))
|
|
||||||
const locked = computed(() => userStore.achievements.filter(a => !a.unlocked))
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (auth.user) void userStore.fetchStudentData()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="achievements page-content">
|
|
||||||
<h1 class="page-title">Достижения</h1>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2 class="section-title">Полученные достижения</h2>
|
|
||||||
<EmptyState
|
|
||||||
v-if="!unlocked.length"
|
|
||||||
title="Полученных достижений пока нет"
|
|
||||||
subtitle="Они появятся после участия в лекциях и отзывах."
|
|
||||||
/>
|
|
||||||
<div v-else class="list">
|
|
||||||
<AchievementBadge
|
|
||||||
v-for="a in unlocked"
|
|
||||||
:key="a.id"
|
|
||||||
:icon="a.icon"
|
|
||||||
:title="a.title"
|
|
||||||
:description="a.description"
|
|
||||||
:unlocked="a.unlocked"
|
|
||||||
:unlockedAt="a.unlockedAt"
|
|
||||||
:coins="a.coins"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2 class="section-title">Заблокированные</h2>
|
|
||||||
<EmptyState
|
|
||||||
v-if="!locked.length"
|
|
||||||
title="Нет заблокированных достижений"
|
|
||||||
subtitle="Backend пока не вернул список будущих достижений."
|
|
||||||
/>
|
|
||||||
<div v-else class="list">
|
|
||||||
<AchievementBadge
|
|
||||||
v-for="a in locked"
|
|
||||||
:key="a.id"
|
|
||||||
:icon="a.icon"
|
|
||||||
:title="a.title"
|
|
||||||
:description="a.description"
|
|
||||||
:unlocked="a.unlocked"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.achievements { display: flex; flex-direction: column; gap: 20px; }
|
|
||||||
.list { display: flex; flex-direction: column; gap: 12px; }
|
|
||||||
</style>
|
|
||||||
@@ -41,6 +41,8 @@ const levelProgressLabel = computed(() =>
|
|||||||
const levelProgressText = computed(() =>
|
const levelProgressText = computed(() =>
|
||||||
hasNextLevel.value ? `${levelProgress.value} / ${levelProgressMax.value} XP` : `${userXp.value} XP`
|
hasNextLevel.value ? `${levelProgress.value} / ${levelProgressMax.value} XP` : `${userXp.value} XP`
|
||||||
)
|
)
|
||||||
|
const unlockedAchievements = computed(() => userStore.achievements.filter(a => a.unlocked))
|
||||||
|
const lockedAchievements = computed(() => userStore.achievements.filter(a => !a.unlocked))
|
||||||
const interestTags = ref([
|
const interestTags = ref([
|
||||||
{ label: '#ML', active: true },
|
{ label: '#ML', active: true },
|
||||||
{ label: '#ИИ', active: true },
|
{ label: '#ИИ', active: true },
|
||||||
@@ -105,25 +107,52 @@ onMounted(() => {
|
|||||||
Email уведомления
|
Email уведомления
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
</GlassCard>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<GlassCard>
|
||||||
<div class="section-title">Достижения</div>
|
<div class="section-title">Достижения</div>
|
||||||
|
|
||||||
|
<section class="achievements-section">
|
||||||
|
<h2 class="section-title">Полученные достижения</h2>
|
||||||
<EmptyState
|
<EmptyState
|
||||||
v-if="!userStore.achievements.length"
|
v-if="!unlockedAchievements.length"
|
||||||
title="Достижений пока нет"
|
title="Полученных достижений пока нет"
|
||||||
subtitle="Они появятся после посещений и отзывов."
|
subtitle="Они появятся после участия в лекциях и отзывах."
|
||||||
/>
|
/>
|
||||||
<div v-else class="achievements">
|
<div v-else class="achievements-list">
|
||||||
<AchievementBadge
|
<AchievementBadge
|
||||||
v-for="a in userStore.achievements.slice(0, 3)"
|
v-for="a in unlockedAchievements"
|
||||||
:key="a.id"
|
:key="a.id"
|
||||||
:icon="a.icon"
|
:icon="a.icon"
|
||||||
:title="a.title"
|
:title="a.title"
|
||||||
:description="a.description"
|
:description="a.description"
|
||||||
:unlocked="a.unlocked"
|
:unlocked="a.unlocked"
|
||||||
:unlockedAt="a.unlockedAt"
|
:unlockedAt="a.unlockedAt"
|
||||||
|
:coins="a.coins"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</GlassCard>
|
</section>
|
||||||
|
|
||||||
|
<section class="achievements-section">
|
||||||
|
<h2 class="section-title">Заблокированные</h2>
|
||||||
|
<EmptyState
|
||||||
|
v-if="!lockedAchievements.length"
|
||||||
|
title="Нет заблокированных достижений"
|
||||||
|
subtitle="Backend пока не вернул список будущих достижений."
|
||||||
|
/>
|
||||||
|
<div v-else class="achievements-list">
|
||||||
|
<AchievementBadge
|
||||||
|
v-for="a in lockedAchievements"
|
||||||
|
:key="a.id"
|
||||||
|
:icon="a.icon"
|
||||||
|
:title="a.title"
|
||||||
|
:description="a.description"
|
||||||
|
:unlocked="a.unlocked"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
</GlassCard>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -138,7 +167,8 @@ onMounted(() => {
|
|||||||
.level { margin: 16px 0; }
|
.level { margin: 16px 0; }
|
||||||
.level-header { display: flex; justify-content: space-between; font-size: 12px; color: var(--color-text-secondary); margin-bottom: 6px; }
|
.level-header { display: flex; justify-content: space-between; font-size: 12px; color: var(--color-text-secondary); margin-bottom: 6px; }
|
||||||
.tags-grid { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
|
.tags-grid { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
|
||||||
.settings { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
|
.settings { display: flex; flex-direction: column; gap: 8px; }
|
||||||
.setting { font-size: 13px; color: var(--color-text-secondary); display: flex; gap: 8px; align-items: center; }
|
.setting { font-size: 13px; color: var(--color-text-secondary); display: flex; gap: 8px; align-items: center; }
|
||||||
.achievements { display: flex; flex-direction: column; gap: 12px; margin-top: 10px; }
|
.achievements-section { display: flex; flex-direction: column; gap: 12px; margin-top: 18px; }
|
||||||
|
.achievements-list { display: flex; flex-direction: column; gap: 12px; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user