business-card-site/WEB/src/pages/ContestDetailPage.vue

299 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<q-page class="contest-detail-page bg-violet-strong q-pa-md">
<q-btn
icon="arrow_back"
label="Обратно на страницу"
flat
color="white"
@click="router.back()"
class="q-mb-lg"
/>
<div v-if="loading" class="flex flex-center q-pt-xl" style="min-height: 50vh;">
<q-spinner-dots color="white" size="5em" />
</div>
<div v-else-if="contest" class="q-gutter-y-xl">
<div class="flex justify-center q-mb-md">
<q-avatar size="140px" class="contest-logo shadow-12">
<img :src="contest.photo || 'https://cdn.quasar.dev/logo-v2/svg/logo.svg'" alt="Логотип конкурса"/>
</q-avatar>
</div>
<div class="flex justify-center q-mb-xl">
<q-card class="contest-name-card">
<q-card-section class="text-h4 text-center text-indigo-10 q-pa-md">
{{ contest.title }}
</q-card-section>
</q-card>
</div>
<div class="flex justify-center q-mb-xl">
<q-card class="description-card violet-card" style="max-width: 940px; width: 100%;">
<q-card-section>
<div class="text-h6 text-indigo-10 q-mb-sm">Описание</div>
<div class="text-body1 text-indigo-9 q-mb-md">{{ contest.description }}</div>
<div v-if="contest.web_url" class="q-mt-md text-indigo-9">
<q-icon name="link" size="sm" class="q-mr-xs" />
<a :href="contest.web_url" target="_blank" class="text-indigo-9" style="text-decoration: none;">
Перейти на сайт конкурса
</a>
</div>
<div v-if="contest.results" class="q-mt-md text-indigo-9">
<q-icon name="emoji_events" size="sm" class="q-mr-xs" />
Результаты: <span class="text-weight-bold">{{ contest.results }}</span>
</div>
<div v-if="contest.is_win" class="q-mt-md text-indigo-9">
<q-icon name="celebration" size="sm" class="q-mr-xs" />
<span class="text-weight-bold text-positive">Победа!</span>
</div>
</q-card-section>
</q-card>
</div>
<div v-if="contest.carousel_photos && contest.carousel_photos.length > 0" class="flex justify-center q-mb-xl">
<q-card class="violet-card" style="max-width: 940px; width: 100%;">
<q-card-section>
<div class="text-h6 text-indigo-10 q-mb-md">Галерея</div>
<q-carousel
v-model="slide"
transition-prev="slide-right"
transition-next="slide-left"
swipeable
animated
control-color="indigo-10"
navigation
arrows
autoplay
infinite
class="rounded-borders"
height="300px"
>
<q-carousel-slide v-for="(photo, index) in contest.carousel_photos" :key="index" :name="index + 1" :img-src="photo.url" />
</q-carousel>
</q-card-section>
</q-card>
</div>
<div v-if="contest.files && contest.files.length > 0" class="flex justify-center q-mb-xl">
<q-card class="violet-card" style="max-width: 940px; width: 100%;">
<q-card-section>
<div class="text-h6 text-indigo-10 q-mb-md">Файлы</div>
<q-list separator bordered class="rounded-borders">
<q-item v-for="file in contest.files" :key="file.id" clickable v-ripple :href="file.url" target="_blank">
<q-item-section avatar>
<q-icon name="folder_open" color="indigo-8" />
</q-item-section>
<q-item-section>
<q-item-label>{{ file.name }}</q-item-label>
<q-item-label caption>{{ file.description }}</q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="download" color="indigo-6" />
</q-item-section>
</q-item>
</q-list>
</q-card-section>
</q-card>
</div>
<div v-if="contestParticipants.length > 0" class="flex justify-center flex-wrap q-gutter-md q-mb-xl">
<div class="flex justify-center q-mb-md" style="width: 100%;">
<q-card class="contest-name-card"> <q-card-section class="text-h6 text-center text-indigo-10 q-pa-md">
Участники конкурса
</q-card-section>
</q-card>
</div>
<q-card
v-for="member in contestParticipants"
:key="member.id"
class="member-card violet-card"
bordered
style="width: 180px;"
v-ripple
>
<q-card-section class="q-pa-md flex flex-center">
<q-avatar size="64px" class="shadow-6">
<img :src="member.avatar" :alt="member.name"/>
</q-avatar>
</q-card-section>
<q-card-section class="q-pt-none">
<div class="text-subtitle1 text-center text-indigo-11">{{ member.name }}</div>
<div class="text-caption text-center text-indigo-9">{{ member.role }}</div>
</q-card-section>
</q-card>
</div>
<q-separator class="q-my-lg" color="indigo-4" style="width: 80%; margin: 0 auto;"/>
<div class="q-mt-md"></div>
<div v-if="contest.repository_url" class="flex justify-center q-mb-xl">
<q-card class="violet-card" style="max-width: 940px; width: 100%;">
<q-card-section class="q-pa-md">
<div class="text-h6 text-indigo-10 q-mb-md">Репозиторий решения</div>
<div class="text-body1 text-indigo-9">
<q-icon name="code" size="sm" class="q-mr-xs" />
<a :href="contest.repository_url" target="_blank" class="text-indigo-9" style="text-decoration: none; word-break: break-all;">
{{ contest.repository_url }}
</a>
</div>
</q-card-section>
</q-card>
</div>
</div>
<div v-else class="flex flex-center q-pt-xl text-white text-h5" style="min-height: 50vh;">
Конкурс не найден :(
</div>
</q-page>
</template>
<script setup>
import { ref, computed, onMounted, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Ripple, Notify } from 'quasar';
import axios from 'axios';
import CONFIG from "@/core/config.js";
// Директивы
defineExpose({ directives: { ripple: Ripple } });
const route = useRoute();
const router = useRouter();
// --- Состояние конкурса ---
const contest = ref(null);
const loading = ref(true);
const contestId = computed(() => route.params.id);
// --- Карусель фото ---
const slide = ref(1);
// --- Участники конкурса (моковые данные) ---
const contestParticipants = ref([
{ id: 1, name: 'Иван Иванов', role: 'Team Lead', avatar: 'https://randomuser.me/api/portraits/men/32.jpg' },
{ id: 2, name: 'Мария Петрова', role: 'Frontend', avatar: 'https://randomuser.me/api/portraits/women/44.jpg' },
{ id: 3, name: 'Алексей Смирнов', role: 'Backend', avatar: 'https://randomuser.me/api/portraits/men/65.jpg' },
// Добавьте больше участников или динамически загружайте их
]);
// --- Загрузка данных конкурса ---
async function fetchContestDetails(id) {
loading.value = true;
try {
// В реальном приложении здесь будет API-запрос:
// const response = await axios.get(`${CONFIG.BASE_URL}/contests/${id}`);
// contest.value = response.data;
// Моковые данные для примера (замените на реальный fetch)
const mockContests = [
{
id: 1,
title: 'Hackathon 2024',
description: 'Ежегодный хакатон для стартапов, где команды соревнуются в создании инновационных решений за короткий период времени. Фокус на Web3 и AI технологиях.',
web_url: 'https://example.com/hackathon2024',
repository_url: 'https://github.com/my-team/hackathon2024-solution',
photo: 'https://cdn.quasar.dev/img/parallax2.jpg',
results: '1 место в категории "Лучшее AI-решение"',
is_win: true,
carousel_photos: [
{ id: 1, url: 'https://images.unsplash.com/photo-1668796319088-214d6a82d54b?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D' },
{ id: 2, url: 'https://cdn.quasar.dev/img/quasar.jpg' },
{ id: 3, url: 'https://cdn.quasar.dev/img/parallax1.jpg' },
{ id: 4, url: 'https://cdn.quasar.dev/img/donuts.png' },
{ id: 5, url: 'https://cdn.quasar.dev/img/parallax2.jpg' },
],
files: [
{ id: 1, name: 'Презентация проекта.pdf', description: 'Финальная презентация', url: 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf' },
{ id: 2, name: 'Код проекта.zip', description: 'Исходный код', url: 'https://www.learningcontainer.com/wp-content/uploads/2020/07/Example-Zip-File.zip' },
],
project_id: 1,
status_id: 1,
},
{
id: 2,
title: 'CodeFest',
description: 'Масштабное соревнование по спортивному программированию, где участники решают алгоритмические задачи. Отличная возможность проверить свои навыки.',
web_url: 'https://codefest.org',
repository_url: 'https://gitlab.com/awesome-devs/codefest-challenge',
photo: 'https://cdn.quasar.dev/img/material.png',
results: null,
is_win: false,
carousel_photos: [
{ id: 10, url: 'https://images.unsplash.com/photo-1584291378203-674a462de8bc?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D' },
{ id: 11, url: 'https://cdn.quasar.dev/img/chicken-salad.jpg' },
], // Добавлены фото для второго конкурса
files: [],
project_id: 2,
status_id: 2,
},
// Добавьте другие моковые конкурсы по мере необходимости
];
contest.value = mockContests.find(c => c.id === parseInt(id));
if (!contest.value) {
Notify.create({
type: 'negative',
message: 'Конкурс с таким ID не найден.',
icon: 'warning',
});
}
} catch (error) {
console.error('Ошибка загрузки деталей конкурса:', error);
Notify.create({
type: 'negative',
message: 'Не удалось загрузить информацию о конкурсе.',
icon: 'error',
});
contest.value = null; // Сброс, если ошибка
} finally {
loading.value = false;
}
}
onMounted(async () => {
await fetchContestDetails(contestId.value);
});
watch(contestId, async (newId) => {
if (newId) {
await fetchContestDetails(newId);
}
});
</script>
<style scoped>
.contest-detail-page {
background: linear-gradient(135deg, #4f046f 0%, #7c3aed 100%);
min-height: 100vh;
}
.contest-logo {
border: 4px solid #ede9fe;
}
.contest-name-card {
border-radius: 22px;
background: #ede9fe;
box-shadow: 0 6px 32px rgba(124, 58, 237, 0.13), 0 1.5px 6px rgba(124, 58, 237, 0.10);
padding: 10px 20px;
}
.violet-card {
border-radius: 22px;
background: #ede9fe;
box-shadow: 0 6px 32px rgba(124, 58, 237, 0.13), 0 1.5px 6px rgba(124, 58, 237, 0.10);
}
.member-card {
transition: transform 0.2s ease-in-out;
}
.member-card:hover {
transform: translateY(-5px);
}
</style>