/
MashkoA
/
web-nodejs
Обзор
Документация
Войти
/
MashkoA
/
web-nodejs
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
public/lab9/task2/script.js
326 строк
14 KB
umarovazizjohn
9 labs omg
23 апр 2026, 09:57
23 апр 2026, 09:57
6dff9fc
Код
Авторство
О чём код?
'use strict'; const API = '/api/lab9/products'; const STOCK_API = '/api/lab9/task1/stock'; // Форматирование цены: Копейки -> Рубли const formatPrice = (cents) => { return (cents / 100).toLocaleString('ru-RU', { style: 'currency', currency: 'RUB' }); }; // Показать сообщение function showMessage(message, type = 'error') { const existingMessage = document.querySelector('.message'); if (existingMessage) { existingMessage.remove(); } const messageDiv = document.createElement('div'); messageDiv.className = `message ${type}`; messageDiv.textContent = message; const contentContainer = document.querySelector('.content-container'); contentContainer.insertBefore(messageDiv, contentContainer.firstChild); if (type === 'success') { setTimeout(() => { messageDiv.remove(); }, 5000); } } // Поиск товара по ID async function searchProductById() { const productId = document.getElementById('searchInput').value.trim(); if (!productId) { showMessage('Введите ID товара'); return; } try { document.getElementById('list').innerHTML = '<div class="loading">Поиск товара...</div>'; const res = await fetch(`${API}/${productId}`); if (!res.ok) { if (res.status === 404) { throw new Error('Товар с таким ID не найден'); } throw new Error(`Ошибка сервера: ${res.status}`); } const product = await res.json(); // Отображаем найденный товар document.getElementById('list').innerHTML = ` <div class="product-card editable-product" data-id="${product.id}"> <div class="product-info"> <div class="product-title-editable"> <input type="text" class="editable-input title-input" value="${product.title}" data-original="${product.title}"> </div> <div class="product-details"> <div class="price-editable"> <span>Цена: </span> <input type="number" class="editable-input price-input" value="${(product.price / 100).toFixed(2)}" step="0.01" min="0.01" data-original="${(product.price / 100).toFixed(2)}"> <span> руб.</span> </div> <div class="amount-controls"> <span>Количество: </span> <button class="btn-amount btn-decrease" onclick="changeAmount(${product.id}, -1)">-</button> <span class="amount-display">${product.amount}</span> <button class="btn-amount btn-increase" onclick="changeAmount(${product.id}, 1)">+</button> </div> </div> </div> <div class="product-actions"> <button class="btn btn-save" onclick="saveProduct(${product.id})" disabled>Сохранить</button> <button class="btn btn-delete" onclick="deleteProduct(${product.id})">Удалить</button> </div> </div> `; // Настраиваем редактирование для найденного товара setupInlineEditing(); } catch (error) { document.getElementById('list').innerHTML = `<div class="error">${error.message}</div>`; } } // Загрузка всех товаров async function loadProducts() { try { document.getElementById('list').innerHTML = '<div class="loading">Загрузка товаров...</div>'; const res = await fetch(API); if (!res.ok) throw new Error(`Ошибка загрузки: ${res.status}`); const products = await res.json(); if (products.length === 0) { document.getElementById('list').innerHTML = '<div class="loading">Товаров пока нет</div>'; return; } document.getElementById('list').innerHTML = products.map(product => ` <div class="product-card editable-product" data-id="${product.id}"> <div class="product-info"> <div class="product-title-editable"> <input type="text" class="editable-input title-input" value="${product.title}" data-original="${product.title}"> </div> <div class="product-details"> <div class="price-editable"> <span>Цена: </span> <input type="number" class="editable-input price-input" value="${(product.price / 100).toFixed(2)}" step="0.01" min="0.01" data-original="${(product.price / 100).toFixed(2)}"> <span> руб.</span> </div> <div class="amount-controls"> <span>Количество: </span> <button class="btn-amount btn-decrease" onclick="changeAmount(${product.id}, -1)">-</button> <span class="amount-display">${product.amount}</span> <button class="btn-amount btn-increase" onclick="changeAmount(${product.id}, 1)">+</button> </div> </div> </div> <div class="product-actions"> <button class="btn btn-save" onclick="saveProduct(${product.id})" disabled>Сохранить</button> <button class="btn btn-delete" onclick="deleteProduct(${product.id})">Удалить</button> </div> </div> `).join(''); // Добавляем обработчики изменений для инлайн-редактирования setupInlineEditing(); } catch (error) { document.getElementById('list').innerHTML = `<div class="error">Ошибка загрузки товаров: ${error.message}</div>`; } } // Настройка инлайн-редактирования function setupInlineEditing() { const editableInputs = document.querySelectorAll('.editable-input'); editableInputs.forEach(input => { input.addEventListener('input', function() { const productCard = this.closest('.editable-product'); const saveButton = productCard.querySelector('.btn-save'); const originalValue = this.getAttribute('data-original'); // Активируем кнопку сохранения если значение изменилось if (this.value !== originalValue) { saveButton.disabled = false; } else { // Проверяем все поля на изменения const titleInput = productCard.querySelector('.title-input'); const priceInput = productCard.querySelector('.price-input'); const hasChanges = titleInput.value !== titleInput.getAttribute('data-original') || priceInput.value !== priceInput.getAttribute('data-original'); saveButton.disabled = !hasChanges; } }); }); } // Изменение количества товара async function changeAmount(productId, delta) { try { const productCard = document.querySelector(`.editable-product[data-id="${productId}"]`); const amountDisplay = productCard.querySelector('.amount-display'); const saveButton = productCard.querySelector('.btn-save'); let currentAmount = parseInt(amountDisplay.textContent); const newAmount = currentAmount + delta; if (newAmount < 0) return; // Не позволяем уходить в отрицательные значения amountDisplay.textContent = newAmount; saveButton.disabled = false; } catch (error) { console.error('Ошибка изменения количества:', error); showMessage('Ошибка изменения количества товара'); } } // Сохранение изменений товара async function saveProduct(productId) { try { const productCard = document.querySelector(`.editable-product[data-id="${productId}"]`); const titleInput = productCard.querySelector('.title-input'); const priceInput = productCard.querySelector('.price-input'); const amountDisplay = productCard.querySelector('.amount-display'); const saveButton = productCard.querySelector('.btn-save'); const updatedData = { title: titleInput.value.trim(), price: Math.round(parseFloat(priceInput.value) * 100), // Рубли -> копейки amount: parseInt(amountDisplay.textContent) }; // Валидация if (!updatedData.title) { throw new Error('Название товара не может быть пустым'); } if (updatedData.price <= 0) { throw new Error('Цена должна быть больше 0'); } if (updatedData.amount < 0) { throw new Error('Количество не может быть отрицательным'); } saveButton.disabled = true; const res = await fetch(`${API}/${productId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updatedData) }); if (!res.ok) { const errorData = await res.json().catch(() => ({})); throw new Error(errorData.error || `Ошибка сервера: ${res.status}`); } // Обновляем оригинальные значения titleInput.setAttribute('data-original', updatedData.title); priceInput.setAttribute('data-original', (updatedData.price / 100).toFixed(2)); showMessage('Товар успешно обновлен', 'success'); loadStockStats(); // Обновляем статистику } catch (error) { console.error('Ошибка сохранения товара:', error); showMessage(error.message); // Восстанавливаем кнопку сохранения const saveButton = document.querySelector(`.editable-product[data-id="${productId}"] .btn-save`); saveButton.disabled = false; } } // Удаление товара async function deleteProduct(id) { if (!confirm('Вы уверены, что хотите удалить этот товар?')) return; try { const res = await fetch(`${API}/${id}`, { method: 'DELETE' }); if (!res.ok) throw new Error(`Ошибка удаления: ${res.status}`); showMessage('Товар успешно удален', 'success'); loadProducts(); // Возвращаемся к полному списку после удаления loadStockStats(); } catch (error) { showMessage(`Ошибка удаления товара: ${error.message}`); } } // Обработчик формы добавления товара document.getElementById('addForm').addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(e.target); const submitButton = e.target.querySelector('button[type="submit"]'); const originalText = submitButton.textContent; try { submitButton.textContent = 'Сохранение...'; submitButton.disabled = true; const priceRub = parseFloat(formData.get('price')); const priceCents = Math.round(priceRub * 100); const data = { title: formData.get('title').trim(), price: priceCents, amount: parseInt(formData.get('amount')) }; if (!data.title) throw new Error('Название товара не может быть пустым'); if (data.price <= 0) throw new Error('Цена должна быть больше 0'); if (data.amount < 0) throw new Error('Количество не может быть отрицательным'); const res = await fetch(API, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!res.ok) throw new Error(`Ошибка сервера: ${res.status}`); e.target.reset(); showMessage('Товар успешно добавлен', 'success'); loadProducts(); // Обновляем список после добавления } catch (error) { showMessage(error.message); } finally { submitButton.textContent = originalText; submitButton.disabled = false; } }); // Инициализация при загрузке страницы document.addEventListener('DOMContentLoaded', function() { loadProducts(); // Обработчик поиска по ID document.getElementById('searchBtn').addEventListener('click', searchProductById); // Обработчик очистки поиска document.getElementById('clearSearch').addEventListener('click', () => { document.getElementById('searchInput').value = ''; loadProducts(); }); // Поиск по Enter document.getElementById('searchInput').addEventListener('keypress', (e) => { if (e.key === 'Enter') { searchProductById(); } }); });