feat: первое подключение фронтенда
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 8s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 54s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 27s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 6s

This commit is contained in:
2026-05-11 01:33:38 +03:00
parent 71e7d84e0f
commit 779b6aba77
21 changed files with 942 additions and 365 deletions
@@ -1,25 +1,43 @@
<script setup lang="ts">
import { computed } from 'vue'
import { computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useLecturesStore } from '@/stores/lectures'
import GlassCard from '@/components/ui/GlassCard.vue'
import LectureCard from '@/components/ui/LectureCard.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue'
import EmptyState from '@/components/ui/EmptyState.vue'
const route = useRoute()
const router = useRouter()
const lecturesStore = useLecturesStore()
const lecture = computed(() => lecturesStore.all.find(l => l.id === route.params.id) ?? lecturesStore.all[0]!)
const isRegistered = computed(() => lecturesStore.isRegistered(lecture.value.id))
const attendedLectures = ['1']
const isAttended = computed(() => attendedLectures.includes(lecture.value.id))
const lectureId = computed(() => String(route.params.id))
const lecture = computed(() => lecturesStore.all.find(l => l.id === lectureId.value))
const isRegistered = computed(() => (lecture.value ? lecturesStore.isRegistered(lecture.value.id) : false))
const isAttended = computed(() => lecture.value?.status === 'completed')
const reviews = computed(() => lecturesStore.reviewsByLecture[lectureId.value] ?? [])
const similarLectures = computed(() => lecturesStore.all.filter(l => l.id !== lecture.value.id).slice(0, 3))
const similarLectures = computed(() => lecturesStore.all.filter(l => l.id !== lectureId.value).slice(0, 3))
onMounted(async () => {
if (!lecturesStore.all.length) await lecturesStore.fetchLectures()
await lecturesStore.fetchLecture(lectureId.value)
await lecturesStore.fetchReviews(lectureId.value)
})
</script>
<template>
<div class="lecture-detail page-content">
<div v-if="lecturesStore.loading && !lecture" class="lecture-detail page-content">
<GlassCard>
<div class="text-secondary">Загружаем лекцию...</div>
</GlassCard>
</div>
<div v-else-if="!lecture" class="lecture-detail page-content">
<EmptyState title="Лекция не найдена" :subtitle="lecturesStore.error ?? 'Попробуйте открыть каталог и выбрать лекцию заново.'" />
</div>
<div v-else class="lecture-detail page-content">
<div class="header">
<div>
<div class="breadcrumb">Каталог / {{ lecture.title }}</div>
@@ -73,16 +91,13 @@ const similarLectures = computed(() => lecturesStore.all.filter(l => l.id !== le
Студенты отмечают «понятные примеры» и «много практики». Предлагается добавить больше времени на вопросы и
прикладные кейсы. Средняя оценка 4.8/5.
</p>
<div class="reviews">
<div class="review">
<div class="review-head">Анонимный отзыв · 5 </div>
<div class="review-body">Очень структурно, понравились живые примеры и объяснение базовых концепций.</div>
</div>
<div class="review">
<div class="review-head">Анонимный отзыв · 4 </div>
<div class="review-body">Полезно, но хотелось больше времени на практику и разбор домашних заданий.</div>
<div class="reviews" v-if="reviews.length">
<div v-for="review in reviews" :key="review.id" class="review">
<div class="review-head">{{ review.userName }} · {{ review.sentiment }}</div>
<div class="review-body">{{ review.text }}</div>
</div>
</div>
<p v-else class="text-secondary text-sm">Отзывов пока нет.</p>
</GlassCard>
</div>