Dev #11
@@ -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'] },
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user