/
varsl
/
HTML-CSS
Обзор
Документация
Войти
/
varsl
/
HTML-CSS
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
frontend/js/ticket.js
729 строк
25 KB
varsl
Добавление JavaScript логики
21 дек 2025, 23:16
21 дек 2025, 23:16
9dd2130
Код
Авторство
О чём код?
class TicketSystem { constructor() { this.ticketTypes = []; this.selectedTickets = {}; this.currentDate = new Date(); } async init() { await this.loadTicketTypes(); this.setupEventListeners(); this.initDatePicker(); this.initVisitorCounter(); this.updateTotal(); this.updateSummary(); } async loadTicketTypes() { try { const response = await fetch('assets/data/tickets.json'); this.ticketTypes = await response.json(); } catch (error) { console.error('Ошибка загрузки типов билетов:', error); this.loadDefaultTickets(); } } loadDefaultTickets() { this.ticketTypes = [ { id: 'adult', name: 'Взрослый', price: 1200, description: 'Для посетителей от 18 лет', includes: ['Доступ ко всем зонам', 'Карта зоопарка', 'Посещение шоу'], color: '#2E7D32', maxPerOrder: 10 }, { id: 'child', name: 'Детский', price: 600, description: 'Для детей от 3 до 17 лет', includes: ['Доступ ко всем зонам', 'Детская карта', 'Игровая зона'], color: '#2196F3', maxPerOrder: 10 }, { id: 'student', name: 'Студенческий', price: 800, description: 'При предъявлении студенческого билета', includes: ['Доступ ко всем зонам', 'Карта зоопарка'], color: '#FF9800', maxPerOrder: 5 }, { id: 'senior', name: 'Пенсионный', price: 700, description: 'Для посетителей от 60 лет', includes: ['Доступ ко всем зонам', 'Карта зоопарка'], color: '#9C27B0', maxPerOrder: 10 }, { id: 'family', name: 'Семейный', price: 3000, description: '2 взрослых + 2 ребенка', includes: ['Доступ ко всем зонам', 'Семейная карта', 'Скидка в кафе'], color: '#E91E63', maxPerOrder: 2 }, { id: 'group', name: 'Групповой', price: 900, description: 'От 10 человек', includes: ['Доступ ко всем зонам', 'Экскурсовод', 'Групповая скидка'], color: '#795548', maxPerOrder: 50 } ]; } setupEventListeners() { this.setupQuantityControls(); this.setupDateSelection(); this.setupTimeSelection(); this.setupPaymentOptions(); this.setupPromoCode(); this.setupCheckout(); } setupQuantityControls() { document.addEventListener('click', (e) => { if (e.target.closest('.quantity-btn')) { const button = e.target.closest('.quantity-btn'); const ticketId = button.getAttribute('data-ticket'); const isPlus = button.classList.contains('plus'); this.updateQuantity(ticketId, isPlus); } }); document.addEventListener('input', (e) => { if (e.target.classList.contains('quantity-input')) { const input = e.target; const ticketId = input.id.replace('quantity-', ''); const value = parseInt(input.value) || 0; this.setQuantity(ticketId, value); } }); } updateQuantity(ticketId, isPlus) { const input = document.getElementById(`quantity-${ticketId}`); if (!input) return; let currentValue = parseInt(input.value) || 0; const max = parseInt(input.max); if (isPlus) { if (currentValue < max) { currentValue++; } } else { if (currentValue > 0) { currentValue--; } } this.setQuantity(ticketId, currentValue); } setQuantity(ticketId, quantity) { const input = document.getElementById(`quantity-${ticketId}`); if (!input) return; const max = parseInt(input.max); const validQuantity = Math.min(Math.max(0, quantity), max); input.value = validQuantity; this.selectedTickets[ticketId] = validQuantity; this.updateSubtotal(ticketId); this.updateTotal(); this.updateSummary(); } updateSubtotal(ticketId) { const input = document.getElementById(`quantity-${ticketId}`); const subtotalElement = document.getElementById(`subtotal-${ticketId}`); if (!input || !subtotalElement) return; const quantity = parseInt(input.value) || 0; const price = parseInt(input.getAttribute('data-price')); const subtotal = quantity * price; subtotalElement.textContent = `${subtotal.toLocaleString('ru-RU')} ₽`; subtotalElement.classList.toggle('has-tickets', quantity > 0); } updateTotal() { let total = 0; let totalTickets = 0; this.ticketTypes.forEach(ticket => { const quantity = this.selectedTickets[ticket.id] || 0; total += quantity * ticket.price; totalTickets += quantity; }); const finalTotal = this.applyPromoCode(total); this.updateUI(total, finalTotal, totalTickets); } updateUI(total, finalTotal, totalTickets) { const totalElement = document.getElementById('totalAmount'); const finalTotalElement = document.getElementById('finalTotal'); const ticketCountElement = document.getElementById('ticketCount'); const checkoutBtn = document.getElementById('checkoutBtn'); if (totalElement) { totalElement.textContent = `${total.toLocaleString('ru-RU')} ₽`; } if (finalTotalElement) { finalTotalElement.textContent = `${finalTotal.toLocaleString('ru-RU')} ₽`; } if (ticketCountElement) { ticketCountElement.textContent = totalTickets; ticketCountElement.classList.toggle('visible', totalTickets > 0); } if (checkoutBtn) { checkoutBtn.disabled = totalTickets === 0; checkoutBtn.classList.toggle('disabled', totalTickets === 0); } } initDatePicker() { const dateInput = document.getElementById('visitDate'); if (!dateInput) return; const today = new Date(); const maxDate = new Date(); maxDate.setDate(today.getDate() + 90); dateInput.min = today.toISOString().split('T')[0]; dateInput.max = maxDate.toISOString().split('T')[0]; dateInput.value = today.toISOString().split('T')[0]; dateInput.addEventListener('change', (e) => { this.updateAvailableTimes(e.target.value); }); this.updateAvailableTimes(dateInput.value); } updateAvailableTimes(dateString) { const timeSelect = document.getElementById('visitTime'); if (!timeSelect) return; const date = new Date(dateString); const dayOfWeek = date.getDay(); timeSelect.innerHTML = ''; let times = []; if (dayOfWeek === 0 || dayOfWeek === 6) { times = ['09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00']; } else { times = ['10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00']; } times.forEach(time => { const option = document.createElement('option'); option.value = time; option.textContent = time; timeSelect.appendChild(option); }); timeSelect.value = '10:00'; } initVisitorCounter() { const visitorInput = document.getElementById('visitorCount'); if (!visitorInput) return; visitorInput.addEventListener('input', () => { this.updateGroupDiscount(); }); this.updateGroupDiscount(); } updateGroupDiscount() { const visitorCount = parseInt(document.getElementById('visitorCount').value) || 1; const groupDiscountElement = document.getElementById('groupDiscount'); if (visitorCount >= 10) { groupDiscountElement.textContent = '✓ Доступна групповая скидка 10%'; groupDiscountElement.classList.add('active'); } else { groupDiscountElement.textContent = 'Для групповой скидки нужно минимум 10 человек'; groupDiscountElement.classList.remove('active'); } } setupDateSelection() { const dateButtons = document.querySelectorAll('.date-quick-select'); dateButtons.forEach(button => { button.addEventListener('click', (e) => { const days = parseInt(e.target.getAttribute('data-days')); this.selectDate(days); }); }); } selectDate(daysFromNow) { const dateInput = document.getElementById('visitDate'); if (!dateInput) return; const date = new Date(); date.setDate(date.getDate() + daysFromNow); dateInput.value = date.toISOString().split('T')[0]; this.updateAvailableTimes(dateInput.value); } setupTimeSelection() { const timeButtons = document.querySelectorAll('.time-quick-select'); timeButtons.forEach(button => { button.addEventListener('click', (e) => { const time = e.target.getAttribute('data-time'); this.selectTime(time); }); }); } selectTime(time) { const timeSelect = document.getElementById('visitTime'); if (!timeSelect) return; timeSelect.value = time; } setupPaymentOptions() { const paymentOptions = document.querySelectorAll('input[name="payment"]'); paymentOptions.forEach(option => { option.addEventListener('change', (e) => { this.updatePaymentDetails(e.target.value); }); }); this.updatePaymentDetails('card'); } updatePaymentDetails(paymentMethod) { const cardDetails = document.getElementById('cardDetails'); const paypalDetails = document.getElementById('paypalDetails'); if (cardDetails) { if (paymentMethod === 'card') { cardDetails.classList.add('active'); } else { cardDetails.classList.remove('active'); } } if (paypalDetails) { if (paymentMethod === 'paypal') { paypalDetails.classList.add('active'); } else { paypalDetails.classList.remove('active'); } } } setupPromoCode() { const applyPromoBtn = document.getElementById('applyPromo'); if (applyPromoBtn) { applyPromoBtn.addEventListener('click', () => { this.applyPromoCode(); }); } const promoInput = document.getElementById('promoCode'); if (promoInput) { promoInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.applyPromoCode(); } }); } } applyPromoCode(currentTotal) { const promoInput = document.getElementById('promoCode'); const promoMessage = document.getElementById('promoMessage'); if (!promoInput || !promoMessage) return currentTotal; const promoCode = promoInput.value.trim().toUpperCase(); let discount = 0; let message = ''; let isValid = false; const validPromoCodes = { 'ZOO2025': 15, 'WILDLIFE': 10, 'FAMILY25': 20, 'SUMMER': 5 }; if (promoCode in validPromoCodes) { discount = validPromoCodes[promoCode]; message = `Промокод применен! Скидка ${discount}%`; isValid = true; } else if (promoCode === '') { message = ''; } else { message = 'Неверный промокод'; } promoMessage.textContent = message; promoMessage.className = `promo-message ${isValid ? 'success' : 'error'}`; if (isValid) { const discountAmount = (currentTotal * discount) / 100; const finalTotal = currentTotal - discountAmount; this.updateDiscountDisplay(discountAmount, finalTotal); return finalTotal; } this.updateDiscountDisplay(0, currentTotal); return currentTotal; } updateDiscountDisplay(discountAmount, finalTotal) { const discountElement = document.getElementById('discountAmount'); const finalTotalElement = document.getElementById('finalTotal'); const discountRow = document.querySelector('.discount-row'); if (discountElement && discountRow) { discountElement.textContent = `-${discountAmount.toLocaleString('ru-RU')} ₽`; discountRow.style.display = discountAmount > 0 ? 'flex' : 'none'; } if (finalTotalElement) { finalTotalElement.textContent = `${finalTotal.toLocaleString('ru-RU')} ₽`; } } updateSummary() { const summaryList = document.getElementById('ticketSummary'); if (!summaryList) return; summaryList.innerHTML = ''; let hasTickets = false; this.ticketTypes.forEach(ticket => { const quantity = this.selectedTickets[ticket.id] || 0; if (quantity > 0) { hasTickets = true; const item = document.createElement('div'); item.className = 'summary-item'; item.innerHTML = ` <span>${ticket.name} × ${quantity}</span> <span>${(quantity * ticket.price).toLocaleString('ru-RU')} ₽</span> `; summaryList.appendChild(item); } }); const emptyMessage = document.getElementById('emptySummary'); if (emptyMessage) { emptyMessage.style.display = hasTickets ? 'none' : 'block'; } } setupCheckout() { const checkoutBtn = document.getElementById('checkoutBtn'); if (checkoutBtn) { checkoutBtn.addEventListener('click', (e) => { e.preventDefault(); this.processCheckout(); }); } const checkoutForm = document.getElementById('checkoutForm'); if (checkoutForm) { checkoutForm.addEventListener('submit', (e) => { e.preventDefault(); this.processCheckout(); }); } } async processCheckout() { const totalTickets = Object.values(this.selectedTickets).reduce((a, b) => a + b, 0); if (totalTickets === 0) { this.showError('Выберите хотя бы один билет'); return; } const visitDate = document.getElementById('visitDate').value; const visitTime = document.getElementById('visitTime').value; const visitorName = document.getElementById('visitorName').value; const visitorEmail = document.getElementById('visitorEmail').value; const visitorPhone = document.getElementById('visitorPhone').value; if (!visitorName || !visitorEmail || !visitorPhone) { this.showError('Заполните все обязательные поля'); return; } if (!this.validateEmail(visitorEmail)) { this.showError('Введите корректный email'); return; } if (!this.validatePhone(visitorPhone)) { this.showError('Введите корректный номер телефона'); return; } const checkoutBtn = document.getElementById('checkoutBtn'); const originalText = checkoutBtn.innerHTML; checkoutBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Обработка...'; checkoutBtn.disabled = true; try { const orderData = { tickets: this.selectedTickets, date: visitDate, time: visitTime, customer: { name: visitorName, email: visitorEmail, phone: visitorPhone }, total: this.calculateTotal() }; // Имитация запроса к API await new Promise(resolve => setTimeout(resolve, 1500)); const mockOrder = { id: 'ORD' + Math.random().toString(36).substr(2, 9).toUpperCase(), date: visitDate, time: visitTime, total: this.calculateTotal() }; this.showSuccess(mockOrder); this.resetForm(); } catch (error) { console.error('Ошибка оформления заказа:', error); this.showError('Ошибка при обработке заказа. Попробуйте еще раз.'); } finally { checkoutBtn.innerHTML = originalText; checkoutBtn.disabled = false; } } calculateTotal() { let total = 0; this.ticketTypes.forEach(ticket => { const quantity = this.selectedTickets[ticket.id] || 0; total += quantity * ticket.price; }); return total; } validateEmail(email) { const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return re.test(email); } validatePhone(phone) { const re = /^[\+]?[0-9\s\-\(\)]+$/; return re.test(phone) && phone.replace(/\D/g, '').length >= 10; } showSuccess(order) { const successModal = document.getElementById('successModal'); if (!successModal) return; const orderNumber = document.getElementById('orderNumber'); const orderDate = document.getElementById('orderDate'); const orderTime = document.getElementById('orderTime'); const orderTotal = document.getElementById('orderTotal'); const orderTickets = document.getElementById('orderTickets'); if (orderNumber) orderNumber.textContent = order.id; if (orderDate) { const date = new Date(order.date); orderDate.textContent = date.toLocaleDateString('ru-RU'); } if (orderTime) orderTime.textContent = order.time; if (orderTotal) orderTotal.textContent = `${order.total.toLocaleString('ru-RU')} ₽`; if (orderTickets) { orderTickets.innerHTML = ''; this.ticketTypes.forEach(ticket => { const quantity = this.selectedTickets[ticket.id] || 0; if (quantity > 0) { const item = document.createElement('div'); item.className = 'order-ticket-item'; item.innerHTML = ` <span>${ticket.name} × ${quantity}</span> <span>${(quantity * ticket.price).toLocaleString('ru-RU')} ₽</span> `; orderTickets.appendChild(item); } }); } const printBtn = document.getElementById('printTicket'); if (printBtn) { printBtn.onclick = () => { window.print(); }; } const emailBtn = document.getElementById('emailTicket'); if (emailBtn) { emailBtn.onclick = () => { this.sendEmailConfirmation(order); }; } successModal.classList.add('show'); } async sendEmailConfirmation(order) { try { // Имитация отправки email await new Promise(resolve => setTimeout(resolve, 1000)); this.showNotification('Билет отправлен на email', 'success'); } catch (error) { console.error('Ошибка отправки email:', error); this.showNotification('Ошибка отправки email', 'error'); } } showError(message) { const errorElement = document.createElement('div'); errorElement.className = 'alert alert-error'; errorElement.innerHTML = ` <i class="fas fa-exclamation-circle"></i> <span>${message}</span> <button class="alert-close"> <i class="fas fa-times"></i> </button> `; const container = document.querySelector('.ticket-container') || document.body; container.insertBefore(errorElement, container.firstChild); setTimeout(() => { errorElement.classList.add('show'); }, 10); const closeBtn = errorElement.querySelector('.alert-close'); closeBtn.addEventListener('click', () => { errorElement.classList.remove('show'); setTimeout(() => errorElement.remove(), 300); }); setTimeout(() => { errorElement.classList.remove('show'); setTimeout(() => errorElement.remove(), 300); }, 5000); } showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.innerHTML = ` <div class="notification-content"> <i class="fas fa-${type === 'success' ? 'check-circle' : 'info-circle'}"></i> <span>${message}</span> </div> <button class="notification-close"> <i class="fas fa-times"></i> </button> `; document.body.appendChild(notification); setTimeout(() => { notification.classList.add('show'); }, 10); const closeBtn = notification.querySelector('.notification-close'); closeBtn.addEventListener('click', () => { notification.classList.remove('show'); setTimeout(() => notification.remove(), 300); }); setTimeout(() => { notification.classList.remove('show'); setTimeout(() => notification.remove(), 300); }, 5000); } resetForm() { this.selectedTickets = {}; this.ticketTypes.forEach(ticket => { const input = document.getElementById(`quantity-${ticket.id}`); if (input) { input.value = 0; this.updateSubtotal(ticket.id); } }); document.getElementById('promoCode').value = ''; this.updateTotal(); this.updateSummary(); } } // Инициализация при загрузке страницы document.addEventListener('DOMContentLoaded', function() { const ticketSystem = new TicketSystem(); ticketSystem.init(); // Закрытие модальных окон const modalClose = document.querySelectorAll('.modal-close'); modalClose.forEach(btn => { btn.addEventListener('click', () => { const modal = btn.closest('.modal'); if (modal) { modal.classList.remove('show'); } }); }); window.addEventListener('click', (e) => { if (e.target.classList.contains('modal')) { e.target.classList.remove('show'); } }); // Календарь const calendarBtn = document.getElementById('openCalendar'); if (calendarBtn) { calendarBtn.addEventListener('click', () => { const dateInput = document.getElementById('visitDate'); if (dateInput && dateInput.showPicker) { dateInput.showPicker(); } }); } });