/
itp_practice
/
itp_frontend
Обзор
Документация
Войти
/
itp_practice
/
itp_frontend
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
src/views/SubscriptionsView.vue
296 строк
11 KB
vivanenko
fix subscriptions (profitability)
29 май 2025, 14:52
29 май 2025, 14:52
9171a4e
Код
Авторство
О чём код?
<template> <div class="min-h-screen bg-purple-blue text-white"> <main class="container mx-auto mt-8"> <div class="max-w-6xl mx-auto px-4 py-8"> <!-- Заголовок --> <h1 class="text-4xl font-bold text-center mb-12 text-purple-900"> Подписки </h1> <!-- Карточки тарифов --> <div class="grid md:grid-cols-3 gap-6 mb-12"> <SubscriptionCard v-for="subscription in subscriptions" :key="subscription.id" :subscription="subscription" :is-active="selectedSubscription?.id === subscription.id" @select="selectSubscription(subscription)" @connect="openCartModal" /> </div> <!-- Блок доходности --> <div v-if="selectedSubscription && !loading" class="bg-gradient-to-r from-purple-500 to-blue-500 rounded-xl p-8 mb-12"> <div class="grid md:grid-cols-2 gap-6 text-white"> <div> <h3 class="text-2xl font-bold mb-4">Доходность подписки</h3> <div class="space-y-4"> <div class="flex justify-between items-center"> <span>В месяц:</span> <span class="text-2xl font-bold">₽ {{ selectedSubscription?.monthly }}</span> </div> <div class="flex justify-between items-center"> <span>За пол года:</span> <span class="text-2xl font-bold">₽ {{ selectedSubscription?.halfYear }}</span> </div> </div> </div> <div class="flex items-center justify-center"> <button @click="openCartModal(selectedSubscription)" class="bg-white text-purple-600 px-8 py-3 rounded-full font-bold hover:bg-opacity-90 transition" > Подключить </button> </div> </div> </div> <!-- Список проектов --> <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> <ProjectCard v-for="project in selectedSubscription?.projectsList" :key="project.id" :project="project" /> </div> <!-- Сравнение тарифов --> <section class="plan-comparison py-24"> <div class="container"> <h2 class="section-title text-4xl text-center mb-10">Сравнение <span class="text-primary">тарифов</span></h2> <div class="comparison-table-wrapper overflow-x-auto mt-10 rounded-lg shadow-xl"> <table class="comparison-table w-full border-collapse"> <thead> <tr> <th class="p-4 bg-accent font-semibold text-base text-left">Возможности</th> <th class="p-4 bg-accent font-semibold text-base text-center">Попробовать!</th> <th class="p-4 bg-accent font-semibold text-base text-center">Быстрый старт</th> <th class="p-4 bg-accent font-semibold text-base text-center">Все и сразу</th> </tr> </thead> <tbody> <tr> <td class="p-4 bg-accent/50 font-medium text-left">Срок действия</td> <td class="p-4 bg-accent/30 text-center">7 дней</td> <td class="p-4 bg-accent/20 text-center">30 дней</td> <td class="p-4 bg-accent/30 text-center">30 дней</td> </tr> <tr> <td class="p-4 bg-accent/50 font-medium text-left">Количество проектов</td> <td class="p-4 bg-accent/30 text-center">2</td> <td class="p-4 bg-accent/20 text-center">5</td> <td class="p-4 bg-accent/30 text-center">33+</td> </tr> <tr> <td class="p-4 bg-accent/50 font-medium text-left">Количество аккаунтов</td> <td class="p-4 bg-accent/30 text-center">1</td> <td class="p-4 bg-accent/20 text-center">До 100</td> <td class="p-4 bg-accent/30 text-center">До 100</td> </tr> <tr> <td class="p-4 bg-accent/50 font-medium text-left">Telegram аккаунт в подарок</td> <td class="p-4 bg-accent/30 text-center"><span class="text-red-400 font-bold">Нет</span></td> <td class="p-4 bg-accent/20 text-center"><span class="text-green-400 font-bold">Да</span></td> <td class="p-4 bg-accent/30 text-center"><span class="text-green-400 font-bold">Да</span></td> </tr> <tr> <td class="p-4 bg-accent/50 font-medium text-left">Ежемесячное обновление проектов</td> <td class="p-4 bg-accent/30 text-center"><span class="text-red-400 font-bold">Нет</span></td> <td class="p-4 bg-accent/20 text-center"><span class="text-red-400 font-bold">Нет</span></td> <td class="p-4 bg-accent/30 text-center"><span class="text-green-400 font-bold">Да</span></td> </tr> <tr> <td class="p-4 bg-accent/50 font-medium text-left">Техническая поддержка</td> <td class="p-4 bg-accent/30 text-center"><span class="text-green-400 font-bold">Да</span></td> <td class="p-4 bg-accent/20 text-center"><span class="text-green-400 font-bold">Да</span></td> <td class="p-4 bg-accent/30 text-center"><span class="text-green-400 font-bold">Да</span></td> </tr> <tr> <td class="p-4 bg-accent/50 font-medium text-left">Средний доход в месяц</td> <td class="p-4 bg-accent/30 text-center">$20-40</td> <td class="p-4 bg-accent/20 text-center">$100-200</td> <td class="p-4 bg-accent/30 text-center">$500-1000</td> </tr> </tbody> </table> </div> </div> </section> <!-- CTA --> <section class="cta bg-gradient-primary text-center py-20"> <div class="container"> <h2 class="cta__title text-4xl font-bold mb-5">Готовы начать зарабатывать с TakeToken?</h2> <p class="cta__subtitle text-xl text-white text-opacity-70 mb-10">Выберите подписку и запустите бота уже сегодня</p> <a href="#subscriptions" class="btn btn--primary btn--xl">Выбрать подписку</a> </div> </section> </div> </main> <!-- Cart Modal --> <Cart :is-visible="showCartModal" :selected-tariff="selectedTariffForCart" @close="closeCartModal" @purchase="handlePurchase" /> <!-- Demo Request Modal --> <DemoRequestModal :is-visible="showDemoModal" :selected-tariff="selectedTariffForDemo" @close="closeDemoModal" @submit="handleDemoSubmit" /> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import ProjectCard from "@/components/subscriptions/ProjectCard.vue"; import SubscriptionCard from "@/components/subscriptions/SubscriptionCard.vue"; import Cart from "@/components/subscriptions/Cart.vue"; import DemoRequestModal from "@/components/subscriptions/DemoRequestModal.vue"; import { getApiUrl } from '@/config'; import { useAlerts } from '@/composables/useAlerts'; const { showSuccess } = useAlerts(); // Reactive data const subscriptions = ref([]); const selectedSubscription = ref(null); const showCartModal = ref(false); const selectedTariffForCart = ref(null); const showDemoModal = ref(false); const selectedTariffForDemo = ref(null); const loading = ref(false); const error = ref(null); // Methods const fetchTariffs = async () => { loading.value = true; error.value = null; try { const response = await fetch(`${getApiUrl()}/tariffs`, { method: 'GET', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); if (result.data) { // Получаем родительские тарифы с дочерними const parentTariffs = result.data; // Преобразуем данные для совместимости с существующими компонентами subscriptions.value = parentTariffs.map(tariff => ({ id: tariff.id, title: tariff.name, name: tariff.name, price: Number(getMinPrice(tariff.children)), projects: tariff.projects || 1, // Заглушка gradient: getGradientByIndex(tariff.id), children: tariff.children || [], projectsList: tariff.projectsList || [], monthly: Number((tariff.profitability || 0)), halfYear:Number((tariff.profitability || 0) * 6), })); // Выбираем первый тариф по умолчанию if (subscriptions.value.length > 0) { selectedSubscription.value = subscriptions.value[0]; } } } catch (err) { console.error('Error fetching tariffs:', err); error.value = 'Ошибка загрузки тарифов: ' + err.message; } finally { loading.value = false; } }; const getMinPrice = (children) => { if (!children || children.length === 0) return 0; const prices = children.map(child => parseFloat(child.price || 0)); return Math.min(...prices); }; const getGradientByIndex = (index) => { const gradients = [ 'from-purple-600 to-blue-500', 'from-green-500 to-teal-500', 'from-orange-500 to-red-500' ]; return gradients[(index - 1) % gradients.length]; }; const selectSubscription = (sub) => { selectedSubscription.value = sub; }; const openCartModal = (subscription) => { if (Number(subscription.price) === 0) { selectedTariffForDemo.value = subscription; showDemoModal.value = true; } else { selectedTariffForCart.value = subscription; showCartModal.value = true; } }; const closeCartModal = () => { showCartModal.value = false; selectedTariffForCart.value = null; }; const handlePurchase = (purchaseData) => { console.log('Purchase data:', purchaseData); closeCartModal(); showSuccess('Покупка успешно оформлена!'); }; const closeDemoModal = () => { showDemoModal.value = false; selectedTariffForDemo.value = null; }; const handleDemoSubmit = (data) => { console.log('Demo request data:', data); closeDemoModal(); showSuccess('Заявка успешно отправлена!'); }; // Lifecycle onMounted(() => { fetchTariffs(); }); </script> <style scoped> .modal-demo { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: clamp(320px, 80vw, 938px); max-height: 78vh; width: calc(100% - clamp(80px, 5vw, 100px)); background: var(--header-bg-trans); border: 1px solid #ad8eff; border-radius: clamp(10px, 1vw, 12px); padding: clamp(24px, 4vw, 40px) clamp(16px, 7vw, 80px); overflow-y: auto; z-index: 99999; scrollbar-width: thin; scrollbar-color: #ad8eff #080520; } </style>