/
runesmaker
/
bookbase
Обзор
Документация
Войти
/
runesmaker
/
bookbase
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
static/app.js
225 строк
10 KB
pivnenkoanton0
feat: Базовая логика
14 июн 2026, 15:28
14 июн 2026, 15:28
b99564b
Код
Авторство
О чём код?
// Глобальная переменная для управления модальным окном Bootstrap let bookModal; // Инициализация при загрузке страницы document.addEventListener('DOMContentLoaded', () => { // Инициализируем модальное окно Bootstrap bookModal = new bootstrap.Modal(document.getElementById('bookModal')); // Загружаем список книг loadBooks(); }); // === 1. ЧТЕНИЕ (GET) === async function loadBooks() { const tbody = document.getElementById('booksTableBody'); const loadingMsg = document.getElementById('loadingMsg'); // Собираем параметры фильтрации из полей ввода const search = document.getElementById('searchInput').value; const status = document.getElementById('statusFilter').value; const is_favorite = document.getElementById('favFilter').checked ? 1 : null; // Формируем URL с параметрами let url = '/books?'; if (search) url += `search=${encodeURIComponent(search)}&`; if (status) url += `status=${encodeURIComponent(status)}&`; if (is_favorite !== null) url += `is_favorite=${is_favorite}&`; try { const response = await fetch(url); // Проверка, что сервер вернул успешный ответ (код 200) if (!response.ok) { throw new Error(`Ошибка сервера: ${response.status}`); } const books = await response.json(); // === ИСПРАВЛЕНИЕ: Скрываем сообщение "Загрузка..." === if (loadingMsg) { loadingMsg.style.display = 'none'; } tbody.innerHTML = ''; // Очищаем таблицу if (books.length === 0) { tbody.innerHTML = '<tr><td colspan="5" class="text-center py-4 text-muted">Книги не найдены. Добавьте первую книгу!</td></tr>'; return; } // Отрисовываем каждую книгу books.forEach(book => { const tr = document.createElement('tr'); const favIcon = book.is_favorite ? '<i class="bi bi-heart-fill text-danger"></i> ' : ''; tr.innerHTML = ` <td> <div class="fw-bold">${favIcon}${escapeHtml(book.title)}</div> <small class="text-muted">${escapeHtml(book.author)}</small> </td> <td>${escapeHtml(book.genre || '-')}</td> <td><span class="badge bg-${getStatusColor(book.status)}">${book.status}</span></td> <td>${book.rating ? book.rating : '-'}</td> <td class="text-end"> <button class="btn btn-sm btn-outline-primary me-1" onclick="editBook(${book.id})" title="Редактировать"> <i class="bi bi-pencil"></i> </button> <button class="btn btn-sm btn-outline-danger" onclick="deleteBook(${book.id})" title="Удалить"> <i class="bi bi-trash"></i> </button> </td> `; tbody.appendChild(tr); }); } catch (error) { console.error('Ошибка загрузки книг:', error); // Скрываем загрузку и показываем ошибку, если что-то пошло не так if (loadingMsg) loadingMsg.style.display = 'none'; tbody.innerHTML = `<tr><td colspan="5" class="text-center py-4 text-danger">Ошибка загрузки данных. Проверьте, запущен ли сервер.<br><small>${error.message}</small></td></tr>`; } } // === 2. СОЗДАНИЕ и ОБНОВЛЕНИЕ (POST / PUT) === async function saveBook() { // Собираем данные из формы const bookId = document.getElementById('bookId').value; const isFav = document.getElementById('is_favorite').checked ? 1 : 0; const bookData = { title: document.getElementById('title').value, author: document.getElementById('author').value, isbn: document.getElementById('isbn').value || null, publisher: document.getElementById('publisher').value || null, publish_year: parseInt(document.getElementById('publish_year').value) || null, pages: parseInt(document.getElementById('pages').value) || null, // <--- ДОБАВЛЕНО genre: document.getElementById('genre').value || null, status: document.getElementById('status').value, rating: parseFloat(document.getElementById('rating').value) || null, binding: document.getElementById('binding').value || null, location: document.getElementById('location').value || null, condition: document.getElementById('condition').value || null, notes: document.getElementById('notes').value || null, is_favorite: isFav }; // Простая валидация if (!bookData.title || !bookData.author) { alert('Название и автор обязательны для заполнения!'); return; } try { let response; if (bookId) { // Если есть ID, значит это ОБНОВЛЕНИЕ (PUT) response = await fetch(`/books/${bookId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(bookData) }); } else { // Если ID нет, значит это СОЗДАНИЕ (POST) response = await fetch('/books', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(bookData) }); } if (response.ok) { bookModal.hide(); // Закрываем модальное окно loadBooks(); // Перезагружаем таблицу clearForm(); // Очищаем форму } else { const error = await response.json(); alert('Ошибка: ' + error.detail); } } catch (error) { console.error('Ошибка сохранения:', error); alert('Произошла ошибка при сохранении'); } } // === 3. ПОДГОТОВКА К РЕДАКТИРОВАНИЮ === async function editBook(id) { try { const response = await fetch(`/books/${id}`); if (!response.ok) throw new Error('Книга не найдена'); const book = await response.json(); // Заполняем форму данными из книги document.getElementById('bookId').value = book.id; document.getElementById('title').value = book.title || ''; document.getElementById('author').value = book.author || ''; document.getElementById('isbn').value = book.isbn || ''; document.getElementById('publisher').value = book.publisher || ''; document.getElementById('publish_year').value = book.publish_year || ''; document.getElementById('pages').value = book.pages || ''; // <--- ДОБАВЛЕНО document.getElementById('genre').value = book.genre || ''; document.getElementById('status').value = book.status || 'Планирую'; document.getElementById('rating').value = book.rating || ''; document.getElementById('binding').value = book.binding || ''; document.getElementById('location').value = book.location || ''; document.getElementById('condition').value = book.condition || ''; document.getElementById('notes').value = book.notes || ''; document.getElementById('is_favorite').checked = book.is_favorite === 1; document.getElementById('modalTitle').innerText = 'Редактировать книгу'; bookModal.show(); } catch (error) { alert('Ошибка загрузки данных книги'); } } // === 4. УДАЛЕНИЕ (DELETE) === async function deleteBook(id) { if (!confirm('Вы уверены, что хотите удалить эту книгу?')) return; try { const response = await fetch(`/books/${id}`, { method: 'DELETE' }); if (response.ok) { loadBooks(); // Перезагружаем таблицу после удаления } else { alert('Ошибка при удалении'); } } catch (error) { console.error('Ошибка удаления:', error); alert('Произошла ошибка при удалении'); } } // === ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ === // Открыть модальное окно для новой книги function openModal() { clearForm(); document.getElementById('modalTitle').innerText = 'Добавить книгу'; bookModal.show(); } // Очистить форму function clearForm() { document.getElementById('bookForm').reset(); document.getElementById('bookId').value = ''; } // Безопасный вывод текста (защита от XSS) function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Цвет бейджика в зависимости от статуса function getStatusColor(status) { const colors = { 'Планирую': 'secondary', 'Читаю': 'primary', 'Прочитано': 'success', 'Брошено': 'danger' }; return colors[status] || 'secondary'; }