refactor: перенёс достижения в профиль

This commit is contained in:
2026-05-18 03:41:49 +03:00
parent 19ea303782
commit 2e4ccad894
4 changed files with 40 additions and 79 deletions
@@ -13,7 +13,6 @@ const navItems: NavItem[] = [
{ label: 'Главная', icon: 'home', to: '/', roles: ['student'] },
{ label: 'Каталог', icon: 'books', to: '/catalog', 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'] },
// Teacher
{ label: 'Дашборд', icon: 'chart-bar', to: '/teacher', roles: ['teacher'] },
-1
View File
@@ -19,7 +19,6 @@ const router = createRouter({
{ 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: '/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') },
// 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>
+40 -10
View File
@@ -41,6 +41,8 @@ const levelProgressLabel = computed(() =>
const levelProgressText = computed(() =>
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([
{ label: '#ML', active: true },
{ label: '#ИИ', active: true },
@@ -105,25 +107,52 @@ onMounted(() => {
Email уведомления
</label>
</div>
<div class="section-title">Достижения</div>
</GlassCard>
</div>
<GlassCard>
<div class="section-title">Достижения</div>
<section class="achievements-section">
<h2 class="section-title">Полученные достижения</h2>
<EmptyState
v-if="!userStore.achievements.length"
title="Достижений пока нет"
subtitle="Они появятся после посещений и отзывов."
v-if="!unlockedAchievements.length"
title="Полученных достижений пока нет"
subtitle="Они появятся после участия в лекциях и отзывах."
/>
<div v-else class="achievements">
<div v-else class="achievements-list">
<AchievementBadge
v-for="a in userStore.achievements.slice(0, 3)"
v-for="a in unlockedAchievements"
:key="a.id"
:icon="a.icon"
:title="a.title"
:description="a.description"
:unlocked="a.unlocked"
:unlockedAt="a.unlockedAt"
:coins="a.coins"
/>
</div>
</GlassCard>
</div>
</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>
</section>
</GlassCard>
</div>
</template>
@@ -138,7 +167,8 @@ onMounted(() => {
.level { margin: 16px 0; }
.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; }
.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; }
.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>