299 lines
12 KiB
Vue
299 lines
12 KiB
Vue
<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> |