/
erioxis
/
web-nodejs
Обзор
Документация
Войти
/
erioxis
/
web-nodejs
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
public/lab10/task1/script.js
784 строки
34 KB
erioxis
lab11fix
15 дек 2025, 23:21
15 дек 2025, 23:21
70dc7ba
Код
Авторство
О чём код?
// public/lab10/task1/script.js let allProducts = []; // Глобальная переменная для хранения всех товаров let allCategories = []; // Глобальная переменная для хранения всех категорий let allOrders = []; // Глобальная переменная для хранения всех заказов let cart = []; // Глобальная переменная для хранения корзины let currentProductDetails = null; // Глобальная переменная для хранения данных товара, открытого в модальном окне function showContent(contentId) { document.querySelectorAll('.content').forEach(el => el.classList.remove('active')); document.getElementById(contentId).classList.add('active'); if (contentId === 'store') { loadStoreData(); } else if (contentId === 'orders') { loadOrders(); } else if (contentId === 'management') { loadManagementData(); } } function switchManagementTab(tabId) { document.querySelectorAll('#management .tab-content').forEach(el => el.classList.remove('active')); document.querySelectorAll('#management .tab').forEach(el => el.classList.remove('active')); document.getElementById(tabId).classList.add('active'); document.querySelector(`#management .tab[onclick="switchManagementTab('${tabId}')"]`).classList.add('active'); } // --- Загрузка данных --- async function loadStoreData() { showLoading('products-container'); // Загружаем категории СНАЧАЛА, чтобы заполнить фильтр await loadCategoriesForFilters(); // Затем загружаем все товары await loadAllProducts(); // Отображаем все товары displayProducts(allProducts); // Обновляем отображение корзины updateCartDisplay(); } async function loadOrders() { showLoading('orders-list'); try { const response = await fetch('/api/lab10/task1/all-orders'); allOrders = await response.json(); let html = `<h3>Список всех заказов (${allOrders.length})</h3>`; if (allOrders.length === 0) { html += '<p>Заказов пока нет.</p>'; } else { html += '<ul>'; allOrders.forEach(order => { html += ` <li> <strong>Заказ #${order.id}</strong> от ${new Date(order.created_at).toLocaleString()}<br> Товар: ${order.product_title} - ${order.product_price} руб. x ${order.quantity} шт. <button onclick="viewOrderDetails(${order.id})">Подробнее</button> </li> `; }); html += '</ul>'; } document.getElementById('orders-list').innerHTML = html; } catch (error) { showError('orders-list', 'Ошибка при загрузке заказов: ' + error.message); } } async function loadManagementData() { // Загружаем категории СНАЧАЛА, чтобы заполнить форму добавления/редактирования товара await loadCategoriesForManagement(); // Затем загружаем товары и категории для списков управления await Promise.all([ loadProductsForManagement(), // loadCategoriesForManagement() уже вызван выше ]); } async function loadAllProducts() { try { const response = await fetch('/api/lab10/task1/all-products'); allProducts = await response.json(); } catch (error) { console.error('Ошибка при загрузке товаров:', error); allProducts = []; showError('products-container', 'Ошибка при загрузке товаров: ' + error.message); } } async function loadProductsForManagement() { showLoading('products-list'); try { const response = await fetch('/api/lab10/task1/all-products'); const products = await response.json(); displayProductsManagement(products); } catch (error) { showError('products-list', 'Ошибка при загрузке товаров: ' + error.message); } } async function loadCategoriesForManagement() { try { const response = await fetch('/api/lab10/categories'); allCategories = await response.json(); populateCategoryForForm(allCategories); // Заполняем список для формы displayCategoriesManagement(allCategories); // Заполняем список для отображения // Также заполняем список в модальном окне populateCategoryForFormModal(allCategories); } catch (error) { console.error('Ошибка при загрузке категорий для управления:', error); allCategories = []; populateCategoryForForm(allCategories); populateCategoryForFormModal(allCategories); displayCategoriesManagement(allCategories); // Не вызываем showError здесь, так как это фоновая загрузка } } async function loadCategoriesForFilters() { try { const response = await fetch('/api/lab10/categories'); const categories = await response.json(); populateCategoryFilter(categories); } catch (error) { console.error('Ошибка при загрузке категорий для фильтра:', error); // Даже при ошибке, заполняем пустым массивом, чтобы не сломался фильтр populateCategoryFilter([]); } } // --- Отображение данных --- function displayProducts(products) { const container = document.getElementById('products-container'); if (products.length === 0) { container.innerHTML = '<p>Товары не найдены.</p>'; return; } let html = ''; products.forEach(product => { html += ` <div class="product-card"> ${product.image ? `<img src="${product.image}" alt="${product.title}">` : '<p>Изображение отсутствует</p>'} <h3>${product.title}</h3> <p><strong>Цена:</strong> ${product.price} руб.</p> <p><strong>Количество:</strong> ${product.amount}</p> <p><strong>Категория:</strong> ${product.category_title || 'Без категории'}</p> <button class="btn" onclick="viewProductDetails(${product.id})">Подробнее</button> <button class="btn" onclick="addToCart(${product.id})">В корзину</button> </div> `; }); container.innerHTML = html; } function displayProductsManagement(products) { const container = document.getElementById('products-list'); let html = `<h4>Список товаров (${products.length})</h4>`; if (products.length === 0) { html += '<p>Товаров нет.</p>'; } else { html += '<ul>'; products.forEach(product => { html += ` <li> <strong>${product.title}</strong> - ${product.price} руб., ${product.amount} шт. (Кат.: ${product.category_title || 'None'}) <button class="btn" onclick="editProduct(${product.id})">Ред.</button> <button class="btn btn-danger" onclick="deleteProduct(${product.id})">Удалить</button> </li> `; }); html += '</ul>'; } container.innerHTML = html; } function displayCategoriesManagement(categories) { const container = document.getElementById('categories-list'); let html = `<h4>Список категорий (${categories.length})</h4>`; if (categories.length === 0) { html += '<p>Категорий нет.</p>'; } else { html += '<ul>'; categories.forEach(category => { html += ` <li> ${category.name} <button class="btn" onclick="editCategory(${category.id})">Ред.</button> <button class="btn btn-danger" onclick="deleteCategory(${category.id})">Удалить</button> </li> `; }); html += '</ul>'; } container.innerHTML = html; } function populateCategoryFilter(categories) { const select = document.getElementById('product-category-filter'); // Очищаем все, кроме первой опции "Все категории" select.innerHTML = '<option value="">Все категории</option>'; categories.forEach(cat => { const option = document.createElement('option'); option.value = cat.id; option.textContent = cat.name; select.appendChild(option); }); } // Функция для заполнения списка категорий в форме управления товарами function populateCategoryForForm(categories) { const select = document.getElementById('product-category-id'); // Очищаем все, кроме первой опции "Без категории" select.innerHTML = '<option value="">Без категории</option>'; categories.forEach(cat => { const option = document.createElement('option'); option.value = cat.id; option.textContent = cat.name; select.appendChild(option); }); } // Функция для заполнения списка категорий в модальном окне редактирования function populateCategoryForFormModal(categories) { const select = document.getElementById('edit-product-category-id'); // Очищаем все, кроме первой опции "Без категории" select.innerHTML = '<option value="">Без категории</option>'; categories.forEach(cat => { const option = document.createElement('option'); option.value = cat.id; option.textContent = cat.name; select.appendChild(option); }); } // --- Фильтрация --- function filterProducts() { const searchTerm = document.getElementById('product-search-input').value.toLowerCase(); const categoryId = document.getElementById('product-category-filter').value; // Может быть "" или числом const filtered = allProducts.filter(product => { const matchesSearch = product.title.toLowerCase().includes(searchTerm); // Если categoryId пустой, фильтр не применяется (все категории) const matchesCategory = !categoryId || product.category_id == categoryId; return matchesSearch && matchesCategory; }); displayProducts(filtered); } // --- Корзина --- function addToCart(productId) { const product = allProducts.find(p => p.id === productId); if (!product) { alert('Товар не найден!'); return; } // Проверяем, есть ли товар уже в корзине const existingItem = cart.find(item => item.product.id === productId); if (existingItem) { // Если есть, увеличиваем количество, если оно не превышает склад if (existingItem.quantity < product.amount) { existingItem.quantity++; } else { alert('Невозможно добавить больше товаров: недостаточно на складе.'); } } else { // Если нет, добавляем новый элемент с количеством 1 if (product.amount > 0) { cart.push({ product: product, quantity: 1 }); } else { alert('Товар закончился на складе.'); } } updateCartDisplay(); } function removeFromCart(productId) { cart = cart.filter(item => item.product.id !== productId); updateCartDisplay(); // Обновляем значок в хедере updateCartModalDisplay(); // } function updateCartDisplay() { const badge = document.getElementById('cart-count-badge'); const totalItems = cart.reduce((sum, item) => sum + item.quantity, 0); if (totalItems > 0) { badge.textContent = totalItems; badge.style.display = 'inline'; // Показываем значок } else { badge.style.display = 'none'; // Скрываем значок } } function openCartModal() { updateCartModalDisplay(); // Обновляем содержимое модального окна перед открытием document.getElementById('cartModal').style.display = 'block'; } function closeCartModal() { document.getElementById('cartModal').style.display = 'none'; } function updateCartModalDisplay() { const container = document.getElementById('cart-items-display'); const emptyMessage = document.getElementById('cart-empty-message'); const totalElement = document.getElementById('cart-total-display'); if (cart.length === 0) { container.innerHTML = ''; emptyMessage.style.display = 'block'; totalElement.textContent = 'Общая сумма: 0 руб.'; return; } emptyMessage.style.display = 'none'; let html = ''; let total = 0; cart.forEach(item => { const itemTotal = item.product.price * item.quantity; total += itemTotal; html += ` <div class="cart-item"> <div class="cart-item-info"> <img class="cart-item-image" src="${item.product.image || 'https://via.placeholder.com/50?text=No+Image'}" alt="${item.product.title}"> <div class="cart-item-details"> <strong>${item.product.title}</strong><br> <span>${item.product.price} руб. x ${item.quantity} = ${itemTotal} руб.</span> </div> </div> <div class="cart-item-quantity-controls"> <button class="cart-item-quantity-btn" onclick="changeCartQuantity(${item.product.id}, -1)">-</button> <input type="number" class="cart-item-quantity-input" value="${item.quantity}" min="1" max="${item.product.amount}" onchange="setCartQuantity(${item.product.id}, this.value)" oninput="this.value = Math.min(Math.max(this.value, 1), ${item.product.amount})"> <button class="cart-item-quantity-btn" onclick="changeCartQuantity(${item.product.id}, 1)">+</button> </div> <div> <span class="cart-item-remove" onclick="removeFromCart(${item.product.id})">Удалить</span> </div> </div> `; }); container.innerHTML = html; totalElement.textContent = `Общая сумма: ${total} руб.`; } function changeCartQuantity(productId, delta) { const cartItem = cart.find(item => item.product.id === productId); if (!cartItem) return; const newQuantity = cartItem.quantity + delta; // Проверяем, не превышает ли новое количество доступное на складе if (newQuantity > 0 && newQuantity <= cartItem.product.amount) { cartItem.quantity = newQuantity; updateCartModalDisplay(); // Обновляем только модальное окно updateCartDisplay(); // Обновляем значок в хедере } } function setCartQuantity(productId, newQuantityStr) { const newQuantity = parseInt(newQuantityStr); const cartItem = cart.find(item => item.product.id === productId); if (!cartItem || isNaN(newQuantity)) return; // Проверяем, не превышает ли новое количество доступное на складе if (newQuantity > 0 && newQuantity <= cartItem.product.amount) { cartItem.quantity = newQuantity; updateCartModalDisplay(); // Обновляем только модальное окно updateCartDisplay(); // Обновляем значок в хедере } else { // Если введено недопустимое значение, возвращаем к предыдущему document.querySelector(`.cart-item-quantity-input[value="${newQuantity}"]`).value = cartItem.quantity; } } async function checkoutCart() { if (cart.length === 0) { alert('Корзина пуста.'); return; } if (!confirm('Вы уверены, что хотите оформить заказ на все товары в корзине?')) { return; } // Проверяем доступность товаров на складе для всех товаров в корзине for (const cartItem of cart) { const productInStore = allProducts.find(p => p.id === cartItem.product.id); if (!productInStore || cartItem.quantity > productInStore.amount) { alert(`Недостаточно товара "${cartItem.product.title}" на складе. Доступно: ${productInStore ? productInStore.amount : 0}, запрошено: ${cartItem.quantity}`); return; } } // Отправляем запрос на покупку всех товаров в корзине как одного заказа try { // Создаем один заказ, который будет содержать все товары из корзины // Мы будем отправлять один запрос на покупку каждого уникального товара с его количеством for (const cartItem of cart) { const response = await fetch(`/api/lab10/products/${cartItem.product.id}/buy`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ quantity: cartItem.quantity }), // Передаем количество }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || `Ошибка покупки товара ${cartItem.product.title}`); } } alert('Покупка успешно оформлена!'); // Очищаем корзину cart = []; // Обновляем отображение updateCartDisplay(); updateCartModalDisplay(); // Также обновляем модальное окно closeCartModal(); // Закрываем модальное окно // Перезагружаем список заказов, если находимся на вкладке "Заказы" if (document.getElementById('orders').classList.contains('active')) { loadOrders(); } // Перезагружаем список товаров в магазине, чтобы отразилось изменение количества if (document.getElementById('store').classList.contains('active')) { await loadAllProducts(); filterProducts(); // Применяем текущую фильтрацию } } catch (error) { alert('Ошибка при оформлении заказа: ' + error.message); } } // --- Функции управления --- async function addProduct() { const title = document.getElementById('product-title').value.trim(); const price = parseFloat(document.getElementById('product-price').value); const amount = parseInt(document.getElementById('product-amount').value); // Используем .value, который будет "" или числом const categoryId = document.getElementById('product-category-id').value || null; const image = document.getElementById('product-image').value.trim(); if (!title || isNaN(price) || isNaN(amount) || price < 0 || amount < 0) { showError('products-list', 'Заполните все обязательные поля корректно.'); return; } showLoading('products-list'); try { const response = await fetch('/api/lab10/task1/products', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title, price, amount, category_id: categoryId, image }), }); const data = await response.json(); if (response.ok) { // Очищаем форму document.getElementById('product-title').value = ''; document.getElementById('product-price').value = ''; document.getElementById('product-amount').value = ''; document.getElementById('product-category-id').value = ''; // Сбрасываем на "Без категории" document.getElementById('product-image').value = ''; // Перезагружаем список await loadProductsForManagement(); // Перезагружаем список товаров в магазине, чтобы он появился там if (document.getElementById('store').classList.contains('active')) { await loadAllProducts(); filterProducts(); // Применяем текущую фильтрацию } } else { showError('products-list', data.error || 'Не удалось добавить товар'); } } catch (error) { showError('products-list', 'Ошибка при добавлении товара: ' + error.message); } } async function addCategory() { const name = document.getElementById('category-name').value.trim(); if (!name) { showError('categories-list', 'Введите название категории.'); return; } showLoading('categories-list'); try { const response = await fetch('/api/lab10/task1/categories', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name }), }); const data = await response.json(); if (response.ok) { document.getElementById('category-name').value = ''; await loadCategoriesForManagement(); // Обновляем список и форму // Перезагружаем фильтр категорий в магазине if (document.getElementById('store').classList.contains('active')) { await loadCategoriesForFilters(); await loadAllProducts(); // Перезагрузим товары, так как могли измениться связи filterProducts(); // Применяем текущую фильтрацию } } else { showError('categories-list', data.error || 'Не удалось добавить категорию'); } } catch (error) { showError('categories-list', 'Ошибка при добавлении категории: ' + error.message); } } async function editProduct(productId) { try { const response = await fetch(`/api/lab10/task1/product/${productId}`); const product = await response.json(); if (response.ok && product) { // Заполняем поля модального окна document.getElementById('edit-product-id').value = product.id; document.getElementById('edit-product-title').value = product.title; document.getElementById('edit-product-price').value = product.price; document.getElementById('edit-product-amount').value = product.amount; // Устанавливаем выбранную категорию в модальном окне document.getElementById('edit-product-category-id').value = product.category_id || ''; document.getElementById('edit-product-image').value = product.image || ''; // Обновляем предпросмотр изображения updateImagePreview(); // Показываем модальное окно document.getElementById('editProductModal').style.display = 'block'; } else { alert('Товар не найден или произошла ошибка: ' + (product.error || 'Неизвестная ошибка')); } } catch (error) { alert('Ошибка при загрузке данных товара для редактирования: ' + error.message); } } // Функция для обновления предпросмотра изображения в модальном окне редактирования function updateImagePreview() { const imageUrl = document.getElementById('edit-product-image').value; const preview = document.getElementById('edit-product-image-preview'); if (imageUrl) { preview.src = imageUrl; preview.style.display = 'block'; } else { preview.src = ''; preview.style.display = 'none'; } } async function saveProductEdit() { const id = document.getElementById('edit-product-id').value; const title = document.getElementById('edit-product-title').value.trim(); const price = parseFloat(document.getElementById('edit-product-price').value); const amount = parseInt(document.getElementById('edit-product-amount').value); const categoryId = document.getElementById('edit-product-category-id').value || null; // Может быть "" или числом const image = document.getElementById('edit-product-image').value.trim(); if (!id || !title || isNaN(price) || isNaN(amount) || price < 0 || amount < 0) { alert('Заполните все обязательные поля корректно.'); return; } try { const response = await fetch(`/api/lab10/task1/products/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title, price, amount, category_id: categoryId, image }), }); const data = await response.json(); if (response.ok) { closeEditModal(); await loadProductsForManagement(); // Перезагружаем список товаров в магазине, чтобы изменения отразились if (document.getElementById('store').classList.contains('active')) { await loadAllProducts(); filterProducts(); // Применяем текущую фильтрацию } } else { alert('Не удалось сохранить изменения: ' + (data.error || 'Неизвестная ошибка')); } } catch (error) { alert('Ошибка при сохранении изменений: ' + error.message); } } function closeEditModal() { document.getElementById('editProductModal').style.display = 'none'; } // Закрытие модального окна при клике вне его window.onclick = function(event) { const modal = document.getElementById('editProductModal'); const detailsModal = document.getElementById('productDetailsModal'); const cartModal = document.getElementById('cartModal'); if (event.target === modal) { closeEditModal(); } if (event.target === detailsModal) { closeProductDetailsModal(); } if (event.target === cartModal) { closeCartModal(); } } async function deleteProduct(id) { if (!confirm('Вы уверены, что хотите удалить этот товар? Все связанные заказы также будут удалены.')) { return; } showLoading('products-list'); try { const response = await fetch(`/api/lab10/task1/products/${id}`, { method: 'DELETE', }); const data = await response.json(); if (response.ok) { await loadProductsForManagement(); // Перезагружаем список товаров в магазине, чтобы он исчез оттуда if (document.getElementById('store').classList.contains('active')) { await loadAllProducts(); filterProducts(); // Применяем текущую фильтрацию } } else { alert('Не удалось удалить товар: ' + (data.error || 'Неизвестная ошибка')); } } catch (error) { alert('Ошибка при удалении товара: ' + error.message); } } async function deleteCategory(id) { if (!confirm('Вы уверены, что хотите удалить эту категорию? Все товары в этой категории и связанные с ними заказы будут удалены.')) { return; } showLoading('categories-list'); try { const response = await fetch(`/api/lab10/task1/categories/${id}`, { method: 'DELETE', }); const data = await response.json(); if (response.ok) { await loadCategoriesForManagement(); // Обновляем список и форму if (document.getElementById('store').classList.contains('active')) { await loadCategoriesForFilters(); await loadAllProducts(); // Перезагрузим товары, так как могли измениться связи filterProducts(); } } else { alert('Не удалось удалить категорию: ' + (data.error || 'Неизвестная ошибка')); } } catch (error) { alert('Ошибка при удалении категории: ' + error.message); } } // --- Взаимодействие с товарами и заказами --- async function viewProductDetails(productId) { try { const response = await fetch(`/api/lab10/task1/product/${productId}`); const product = await response.json(); if (response.ok && product) { // Заполняем поля модального окна document.getElementById('modal-product-image').src = product.image || 'https://via.placeholder.com/300?text=No+Image'; document.getElementById('modal-product-title').textContent = product.title; document.getElementById('modal-product-id').textContent = product.id; document.getElementById('modal-product-price').textContent = product.price; document.getElementById('modal-product-amount').textContent = product.amount; document.getElementById('modal-product-category').textContent = product.category_title || 'Без категории'; // Сохраняем данные товара для использования в корзине currentProductDetails = product; // Показываем модальное окно document.getElementById('productDetailsModal').style.display = 'block'; } else { alert('Товар не найден или произошла ошибка: ' + (product.error || 'Неизвестная ошибка')); } } catch (error) { alert('Ошибка при загрузке деталей товара: ' + error.message); } } function closeProductDetailsModal() { document.getElementById('productDetailsModal').style.display = 'none'; currentProductDetails = null; // Очищаем данные } function addToCartFromModal() { if (currentProductDetails) { addToCart(currentProductDetails.id); closeProductDetailsModal(); } } function closeCartModal() { document.getElementById('cartModal').style.display = 'none'; } async function viewOrderDetails(orderId) { try { const response = await fetch(`/api/lab10/task1/orders/${orderId}`); const order = await response.json(); if (response.ok) { let itemsStr = ''; order.items.forEach(item => { itemsStr += `${item.title} - ${item.quantity} шт. × ${item.price} руб. = ${item.total_price} руб.`; }); alert(`Детали заказа #${order.id}:\nДата: ${new Date(order.created_at).toLocaleString()}\nТовары:\n${itemsStr}\nОбщая сумма: ${order.total_price} руб.`); } else { alert('Заказ не найден или произошла ошибка: ' + (order.error || 'Неизвестная ошибка')); } } catch (error) { alert('Ошибка при загрузке деталей заказа: ' + error.message); } } async function buyProduct(productId) { if (!confirm('Вы уверены, что хотите купить этот товар?')) { return; } try { const response = await fetch(`/api/lab10/products/${productId}/buy`, { method: 'POST', }); const data = await response.json(); if (response.ok) { alert(`Покупка успешна! ID заказа: ${data.orderId}`); // Обновим список заказов, если находимся на вкладке "Заказы" if (document.getElementById('orders').classList.contains('active')) { loadOrders(); } // Обновим список товаров в магазине, чтобы отразилось изменение количества if (document.getElementById('store').classList.contains('active')) { await loadAllProducts(); filterProducts(); // Применяем текущую фильтрацию } } else { alert('Ошибка при покупке: ' + (data.error || 'Неизвестная ошибка')); } } catch (error) { alert('Ошибка при оформлении покупки: ' + error.message); } } // --- Вспомогательные функции --- function showError(containerId, message) { document.getElementById(containerId).innerHTML = `<div class="error">${message}</div>`; } function showLoading(containerId) { document.getElementById(containerId).innerHTML = `<div class="loading">Загрузка...</div>`; }