From 632835f834229612453c57238bde0b2732ffded1 Mon Sep 17 00:00:00 2001 From: Baryshev Dmitry Date: Thu, 25 Sep 2025 01:11:17 +0400 Subject: [PATCH] =?UTF-8?q?=D0=B1=D1=80=D0=B0=D1=83=D0=B7=D0=B5=D1=80=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D0=B2=D0=B8=D0=B4=D0=B8=D1=82=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D1=8B,=20=D0=BA?= =?UTF-8?q?=D0=BB=D0=B0=D1=81=D1=81!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- basket.html | 18 ++++- catalog.html | 14 ++++ components/basket/controller.js | 133 ++++++++++++++++++++++++++++++++ components/basket/model.js | 118 ++++++++++++++++++++++++++++ components/basket/view.js | 113 +++++++++++++++++++++++++++ components/likes/controller.js | 0 components/likes/model.js | 0 components/likes/view.js | 0 dist/basket.html | 18 ++++- dist/catalog.html | 14 ++++ 10 files changed, 426 insertions(+), 2 deletions(-) create mode 100644 components/basket/controller.js create mode 100644 components/basket/model.js create mode 100644 components/basket/view.js create mode 100644 components/likes/controller.js create mode 100644 components/likes/model.js create mode 100644 components/likes/view.js diff --git a/basket.html b/basket.html index 090f8c9..5bf1195 100644 --- a/basket.html +++ b/basket.html @@ -58,8 +58,9 @@ } +
-
+

Здесь будут лежать твои товары

А пока здесь так пусто...

Пустая корзина @@ -69,6 +70,8 @@
+ +
+ + + + \ No newline at end of file diff --git a/catalog.html b/catalog.html index 96ffd8a..fbd51ac 100644 --- a/catalog.html +++ b/catalog.html @@ -353,5 +353,19 @@ this.reset(); }); + + + + + \ No newline at end of file diff --git a/components/basket/controller.js b/components/basket/controller.js new file mode 100644 index 0000000..0c16a60 --- /dev/null +++ b/components/basket/controller.js @@ -0,0 +1,133 @@ +// controller.js +export class Controller { + constructor(model, view) { + this.model = model; + this.view = view; + this.init(); + } + + async init() { + // Для страницы корзины + if (window.location.pathname.includes('basket.html')) { + await this.loadBasket(); + this.setupBasketEventListeners(); + } + + // Для страницы каталога + if (window.location.pathname.includes('catalog.html')) { + this.setupCatalogEventListeners(); + } + } + + // Загрузить и отобразить корзину + async loadBasket() { + const basketItems = await this.model.getBasketItems(); + this.view.showBasket(basketItems); + } + + // Настройка обработчиков событий для корзины + setupBasketEventListeners() { + document.addEventListener('click', async (e) => { + const basketItem = e.target.closest('.basket-item'); + if (!basketItem) return; + + const productId = basketItem.dataset.id; + + // Удаление товара + if (e.target.closest('.remove-btn')) { + await this.model.removeFromBasket(productId); + this.view.showNotification('Товар удален из корзины'); + await this.loadBasket(); + } + + // Увеличение количества + if (e.target.closest('.increase-btn')) { + const quantityInput = basketItem.querySelector('.quantity-input'); + const newQuantity = parseInt(quantityInput.value) + 1; + quantityInput.value = newQuantity; + await this.model.updateBasketItem(productId, newQuantity); + await this.loadBasket(); + } + + // Уменьшение количества + if (e.target.closest('.decrease-btn')) { + const quantityInput = basketItem.querySelector('.quantity-input'); + let newQuantity = parseInt(quantityInput.value) - 1; + if (newQuantity < 1) newQuantity = 1; + quantityInput.value = newQuantity; + await this.model.updateBasketItem(productId, newQuantity); + await this.loadBasket(); + } + }); + + // Обработка изменения количества через input + document.addEventListener('change', async (e) => { + if (e.target.classList.contains('quantity-input')) { + const basketItem = e.target.closest('.basket-item'); + const productId = basketItem.dataset.id; + const newQuantity = parseInt(e.target.value) || 1; + + if (newQuantity < 1) { + e.target.value = 1; + return; + } + + await this.model.updateBasketItem(productId, newQuantity); + await this.loadBasket(); + } + }); + + // Оформление заказа + document.addEventListener('click', async (e) => { + if (e.target.id === 'checkoutBtn') { + const basketItems = await this.model.getBasketItems(); + if (basketItems.length === 0) { + this.view.showNotification('Корзина пуста', 'error'); + return; + } + + this.view.showNotification('Заказ оформлен! Спасибо за покупку!'); + await this.model.clearBasket(); + await this.loadBasket(); + } + }); + } + + // Настройка обработчиков событий для каталога + setupCatalogEventListeners() { + document.addEventListener('click', async (e) => { + if (e.target.closest('.btn') && e.target.closest('.btn').textContent.includes('В корзину')) { + const card = e.target.closest('.card'); + const product = this.extractProductData(card); + + if (product) { + await this.model.addToBasket(product); + this.view.showNotification('Товар добавлен в корзину!'); + } + } + }); + } + + extractProductData(card) { + try { + const name = card.querySelector('.card-title').textContent; + const priceText = card.querySelector('.text-muted').textContent.replace('$', ''); + const description = card.querySelector('.card-text').textContent; + const image = card.querySelector('img').src; + + // Генерируем ID на основе содержимого карточки + const id = btoa(`${name}-${priceText}`).substring(0, 8); + + return { + id: id, + name: name.trim(), + price: parseFloat(priceText), + description: description.trim(), + image: image + }; + } catch (error) { + console.error('Ошибка при извлечении данных товара:', error); + return null; + } + } +} \ No newline at end of file diff --git a/components/basket/model.js b/components/basket/model.js new file mode 100644 index 0000000..5ecf422 --- /dev/null +++ b/components/basket/model.js @@ -0,0 +1,118 @@ +// model.js +export class Model { + constructor() { + this.apiUrl = 'http://localhost:3000'; + } + + async request(url, options = {}) { + try { + const response = await fetch(`${this.apiUrl}${url}`, { + ...options, + headers: { + 'Content-Type': 'application/json', + ...options.headers + } + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return await response.json(); + } catch (error) { + console.error('Request failed:', error); + throw error; + } + } + + async getBasketItems() { + try { + return await this.request('/basket'); + } catch (error) { + console.error('Ошибка при получении корзины:', error); + return []; + } + } + + // Добавить товар в корзину + async addToBasket(product) { + try { + // Проверяем, есть ли товар уже в корзине + const basketItems = await this.getBasketItems(); + const existingItem = basketItems.find(item => item.id === product.id); + + if (existingItem) { + // Если товар уже есть, увеличиваем количество + await this.updateBasketItem(product.id, existingItem.quantity + 1); + } else { + // Если товара нет, добавляем новый + const basketItem = { + ...product, + quantity: 1, + addedAt: new Date().toISOString() + }; + + const response = await fetch(`${this.apiUrl}/basket`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(basketItem) + }); + return await response.json(); + } + } catch (error) { + console.error('Ошибка при добавлении в корзину:', error); + } + } + + // Обновить количество товара в корзине + async updateBasketItem(productId, quantity) { + try { + const response = await fetch(`${this.apiUrl}/basket/${productId}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ quantity }) + }); + return await response.json(); + } catch (error) { + console.error('Ошибка при обновлении корзины:', error); + } + } + + // Удалить товар из корзины + async removeFromBasket(productId) { + try { + await fetch(`${this.apiUrl}/basket/${productId}`, { + method: 'DELETE' + }); + } catch (error) { + console.error('Ошибка при удалении из корзины:', error); + } + } + + // Очистить корзину + async clearBasket() { + try { + const basketItems = await this.getBasketItems(); + for (const item of basketItems) { + await this.removeFromBasket(item.id); + } + } catch (error) { + console.error('Ошибка при очистке корзины:', error); + } + } + + // Получить все товары из каталога + async getProducts() { + try { + const response = await fetch(`${this.apiUrl}/shmots`); + return await response.json(); + } catch (error) { + console.error('Ошибка при получении товаров:', error); + return []; + } + } +} \ No newline at end of file diff --git a/components/basket/view.js b/components/basket/view.js new file mode 100644 index 0000000..677d127 --- /dev/null +++ b/components/basket/view.js @@ -0,0 +1,113 @@ +// view.js +export class View { + constructor() { + this.basketContainer = document.getElementById('basketContainer'); + this.emptyBasketElement = document.querySelector('.empty-basket'); + } + + // Показать корзину с товарами + showBasket(items) { + if (items.length === 0) { + this.showEmptyBasket(); + return; + } + + this.hideEmptyBasket(); + + const basketHTML = items.map(item => this.createBasketItemHTML(item)).join(''); + this.basketContainer.innerHTML = ` +
+
+
+
+
Корзина
+
+
+ ${basketHTML} +
+
Итого: $${this.calculateTotal(items).toFixed(2)}
+ +
+
+
+
+
+ `; + } + + // Создать HTML для элемента корзины + createBasketItemHTML(item) { + return ` +
+
+ ${item.name} +
+
+
${item.name}
+

${item.description}

+
+
+ $${item.price} +
+
+
+ + + +
+
+
+ $${(item.price * item.quantity).toFixed(2)} + +
+
+ `; + } + + // Показать пустую корзину + showEmptyBasket() { + if (this.emptyBasketElement) { + this.emptyBasketElement.style.display = 'block'; + } + if (this.basketContainer) { + this.basketContainer.innerHTML = ''; + } + } + + // Скрыть сообщение о пустой корзине + hideEmptyBasket() { + if (this.emptyBasketElement) { + this.emptyBasketElement.style.display = 'none'; + } + } + + // Рассчитать общую сумму + calculateTotal(items) { + return items.reduce((total, item) => total + (item.price * item.quantity), 0); + } + + // Показать уведомление + showNotification(message, type = 'success') { + // Создаем элемент уведомления + const notification = document.createElement('div'); + notification.className = `alert alert-${type === 'success' ? 'success' : 'danger'} alert-dismissible fade show`; + notification.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 1050; min-width: 300px;'; + notification.innerHTML = ` + ${message} + + `; + + document.body.appendChild(notification); + + // Автоматически удаляем через 3 секунды + setTimeout(() => { + if (notification.parentNode) { + notification.remove(); + } + }, 3000); + } +} \ No newline at end of file diff --git a/components/likes/controller.js b/components/likes/controller.js new file mode 100644 index 0000000..e69de29 diff --git a/components/likes/model.js b/components/likes/model.js new file mode 100644 index 0000000..e69de29 diff --git a/components/likes/view.js b/components/likes/view.js new file mode 100644 index 0000000..e69de29 diff --git a/dist/basket.html b/dist/basket.html index e8e1351..d886d49 100644 --- a/dist/basket.html +++ b/dist/basket.html @@ -58,8 +58,9 @@ } +
-
+

Здесь будут лежать твои товары

А пока здесь так пусто...

Пустая корзина @@ -69,6 +70,8 @@
+ +
+ + + + \ No newline at end of file diff --git a/dist/catalog.html b/dist/catalog.html index 90082db..e520ca1 100644 --- a/dist/catalog.html +++ b/dist/catalog.html @@ -353,5 +353,19 @@ this.reset(); }); + + + + + \ No newline at end of file