почти готова к сдаче. добавить норм дизайн к карточке. цвета для вышедшей манги и не вышедшей

This commit is contained in:
2025-05-16 16:25:22 +04:00
parent 8a6503b7be
commit 9f2283f72c
11 changed files with 350 additions and 202 deletions

View File

@@ -55,10 +55,6 @@
</form> </form>
</div> </div>
</div> </div>
<div class="row justify-content-center mt-5" id="userCards">
<!-- Здесь будут карточки -->
</div>
</main> </main>
<footer class="text-center py-4 text-light mt-5 bg-custom-dark"> <footer class="text-center py-4 text-light mt-5 bg-custom-dark">

View File

@@ -16,48 +16,59 @@
<header class=" py-3 bg-custom-dark"> <header class=" py-3 bg-custom-dark">
<div class="container d-flex align-items-center"> <div class="container d-flex align-items-center">
<a href="index.html" class="me-3"> <a class="navbar-brand" href="index.html">
<img src="../img/manga.p, 10ng" alt="ЛОГО" height="50" /> <img src="img/manga.png" alt="ЛОГО" height="50">
</a> </a>
<h1 class="h5 mb-0 text-light">Добавить книгу</h1> <h1 class="h5 ms-4 mb-0 text-light">Добавить книгу</h1>
</div> </div>
</header> </header>
<main class="container mt-5"></main> <main class="container bg-custom-dark mt-5">
<form id="bookForm" class="bg-secondary p-4 rounded"> <form id="bookForm">
<input type="hidden" id="bookId" />
<div class="mb-3"> <div class="mb-3">
<label for="title" class="form-label">Название</label> <label for="title" class="form-label">Название книги</label>
<input type="text" class="form-control" id="title" required> <input type="text" id="title" class="form-control" required />
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="description" class="form-label">Описание</label> <label for="description" class="form-label description">Описание книги</label>
<textarea class="form-control" id="description" rows="3" required></textarea> <textarea id="description" class="description"></textarea>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="authorId" class="form-label">Автор</label> <label for="authorId" class="form-label">Автор</label>
<select class="form-select" id="authorId" required> <select id="authorId" class="form-select" required></select>
<option value="">Выберите автора</option>
<!-- Авторы будут подставлены через JS -->
</select>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="cover" class="form-label">Ссылка на обложку</label> <label for="statusId" class="form-label">Статус</label>
<input type="url" class="form-control" id="cover" required> <select id="statusId" class="form-select" required></select>
</div> </div>
<div class="d-grid">
<button type="submit" class="btn btn-warning"> <div class="mb-3">
<i class="bi bi-bookmark-plus me-2"></i>Добавить книгу <label for="cover" class="form-label">Обложка (файл)</label>
</button> <img id="coverPreview" src="" alt="Превью обложки" class="mt-2" style="max-height: 150px; display:none;" />
<input type="file" id="cover" name="cover" accept="image/*" class="form-control"/>
</div> </div>
<button type="submit" class="btn btn-primary">Сохранить</button>
<button type="button" id="cancelBtn" class="btn btn-secondary ms-2">Отмена</button>
</form> </form>
</main> </main>
<footer class="text-center py-4 text-light mt-5 bg-custom-dark"> <footer class="text-center py-4 text-light mt-5 bg-custom-dark">
<p><i class="bi bi-journal-text me-2"></i>Все права защищены, 2025</p> <p><i class="bi bi-journal-text me-2"></i>Все права защищены, 2025</p>
</footer> </footer>
<script type="module" src="../src/main.js"></script>
<script type="module" src="/mvc/controller.js"></script> <script type="module" src="/mvc/controller.js"></script>
</body> </body>
</html> </html>

View File

@@ -40,3 +40,13 @@ footer {
width: 600px; width: 600px;
} }
.description {
width: 100%;
min-height: 120px;
max-height: 400px;
overflow: hidden; /* Запретить скролл */
resize: none; /* Запретить изменение размера */
box-sizing: border-box;
padding: 8px 12px;
font-size: 1rem;
}

File diff suppressed because one or more lines are too long

View File

@@ -41,69 +41,18 @@
</nav> </nav>
<!-- Основное содержимое --> <!-- Основное содержимое -->
<div class="container mt-5 bg-secondary"> <div class="container mt-5 bg-custom-dark p-3 rounded">
<!-- Раздел с произведениями --> <div class="d-flex justify-content-between align-items-center mb-4">
<div class="main"> <h3>Наши произведения</h3>
<h3 class="text-center">Наши произведения</h3> <a href="add_book.html" class="btn btn-success">
<section class="row row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4 g-4"> <i class="bi bi-plus-circle me-2"></i>Добавить книгу
<div class="col d-flex justify-content-center"> </a>
<a href="manga.html" class="d-block">
<figure class="text-center">
<img src="img/заглушка.jpg" class="img-fluid" alt="Заглушка">
<figcaption>Заглушка</figcaption>
</figure>
</a>
</div>
<div class="col d-flex justify-content-center">
<a href="manga.html" class="d-block">
<figure class="text-center">
<img src="img/заглушка.jpg" class="img-fluid" alt="Заглушка">
<figcaption>Заглушка</figcaption>
</figure>
</a>
</div>
<div class="col d-flex justify-content-center">
<a href="manga.html" class="d-block">
<figure class="text-center">
<img src="img/заглушка.jpg" class="img-fluid" alt="Заглушка">
<figcaption>Заглушка</figcaption>
</figure>
</a>
</div>
<div class="col d-flex justify-content-center">
<a href="manga.html" class="d-block">
<figure class="text-center">
<img src="img/заглушка.jpg" class="img-fluid" alt="Заглушка">
<figcaption>Заглушка</figcaption>
</figure>
</a>
</div>
<div class="col d-flex justify-content-center">
<a href="manga.html" class="d-block">
<figure class="text-center">
<img src="img/заглушка.jpg" class="img-fluid" alt="Заглушка">
<figcaption>Заглушка</figcaption>
</figure>
</a>
</div>
<div class="col d-flex justify-content-center">
<a href="manga.html" class="d-block">
<figure class="text-center">
<img src="img/заглушка.jpg" class="img-fluid" alt="Заглушка">
<figcaption>Заглушка</figcaption>
</figure>
</a>
</div>
<div class="col d-flex justify-content-center">
<a href="manga.html" class="d-block">
<figure class="text-center">
<img src="img/заглушка.jpg" class="img-fluid" alt="Заглушка">
<figcaption>Заглушка</figcaption>
</figure>
</a>
</div>
</section>
</div> </div>
<div id="booksContainer" class="row row-cols-1 row-cols-md-3 g-3">
<!-- Здесь динамически будут отображаться книги -->
</div>
</div>
<!-- Новости --> <!-- Новости -->
<div class="news mt-5"> <div class="news mt-5">
@@ -161,6 +110,7 @@
<!-- JS Bootstrap --> <!-- JS Bootstrap -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/js/bootstrap.bundle.min.js"></script>
<script type="module" src="../src/main.js"></script>
</body> </body>
</html> </html>

View File

@@ -45,41 +45,11 @@
<div class="d-flex justify-content-center gap-3 mt-3"> <div class="d-flex justify-content-center gap-3 mt-3">
<a href="author.html" class="btn btn-primary"><i class="bi bi-person-lines-fill me-2"></i>Про автора</a> <a href="author.html" class="btn btn-primary"><i class="bi bi-person-lines-fill me-2"></i>Про автора</a>
<a href="reading.html" class="btn btn-success"><i class="bi bi-book me-2"></i>Читать</a> <a href="reading.html" class="btn btn-success"><i class="bi bi-book me-2"></i>Читать</a>
<button class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#addReaderModal">
<i class="bi bi-person-plus me-2"></i>Добавить читателя
</button>
</div> </div>
</div> </div>
</div> </div>
</main> </main>
<div class="modal fade" id="addReaderModal" tabindex="-1" aria-labelledby="addReaderModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content bg-dark text-light">
<div class="modal-header">
<h5 class="modal-title" id="addReaderModalLabel">Добавить читателя</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body">
<form id="addReaderForm">
<div class="mb-3">
<label for="userSelect" class="form-label">Выберите пользователя</label>
<select class="form-select" id="userSelect" required>
<option value="">Выберите...</option>
<!-- Пользователи будут подставлены через JS -->
</select>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-success">
<i class="bi bi-check2-circle me-2"></i>Добавить
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<footer class="bg-custom-dark text-light text-center py-4"> <footer class="bg-custom-dark text-light text-center py-4">
<div class="container"> <div class="container">
<p>Спасибо, что посетили наш сайт, если возникли вопросы, обращайтесь к нам на почту <p>Спасибо, что посетили наш сайт, если возникли вопросы, обращайтесь к нам на почту

View File

@@ -1,21 +1,16 @@
const BASE_URL = "http://localhost:5174"; const BASE_URL = "http://localhost:5174"; // Порт json-server
async function request(path, method = "GET", body = null) { async function request(path, method = "GET", body = null) {
const options = { method, headers: { "Content-Type": "application/json" } }; const options = { method, headers: { "Content-Type": "application/json" } };
if (body) options.body = JSON.stringify(body); if (body) options.body = JSON.stringify(body);
const response = await fetch(`${BASE_URL}/${path}`, options); const response = await fetch(`${BASE_URL}/${path}`, options);
if (!response.ok) { if (!response.ok) throw new Error(`Ошибка запроса: ${response.status}`);
throw new Error(`Ошибка запроса: ${response.status}`);
}
return response.json(); return response.json();
} }
const api = { export const getAllItems = (entity) => request(entity);
getAll: (entity) => request(entity), export const getItem = (entity, id) => request(`${entity}/${id}`);
getById: (entity, id) => request(`${entity}/${id}`), export const createItem = (entity, data) => request(entity, "POST", data);
create: (entity, data) => request(entity, "POST", data), export const updateItem = (entity, id, data) => request(`${entity}/${id}`, "PATCH", data);
update: (entity, id, data) => request(`${entity}/${id}`, "PATCH", data), export const deleteItem = (entity, id) => request(`${entity}/${id}`, "DELETE");
delete: (entity, id) => request(`${entity}/${id}`, "DELETE"),
};
export default api;

View File

@@ -2,45 +2,126 @@ import model from "./model";
import view from "./view"; import view from "./view";
const controller = { const controller = {
async init() { // Инициализация главной страницы (index.html)
const addReaderModal = document.getElementById("addReaderModal"); async initIndexPage() {
addReaderModal.addEventListener("show.bs.modal", async () => { await this.loadAuthorsAndStatuses();
await this.loadAndRenderBooks();
// Делегирование событий на контейнер с книгами
const booksContainer = document.getElementById("booksContainer");
if (booksContainer) {
booksContainer.addEventListener("click", async (e) => {
const { id } = e.target.dataset;
if (e.target.classList.contains("delete-btn")) {
if (confirm("Удалить книгу?")) {
try {
await model.deleteBook(id);
view.showAlert("Книга удалена");
await this.loadAndRenderBooks();
} catch (error) {
console.error(error);
view.showAlert("Ошибка при удалении книги", true);
}
}
} else if (e.target.classList.contains("edit-btn")) {
// При редактировании перенаправляем на add_book.html?id=...
window.location.href = `add_book.html?id=${id}`;
}
});
}
},
// Инициализация страницы добавления/редактирования (add_book.html)
async initAddBookPage() {
await this.loadAuthorsAndStatuses();
view.bindCoverInput();
// Если есть id в URL — значит редактирование
const params = new URLSearchParams(window.location.search);
const editingId = params.get("id");
if (editingId) {
try { try {
const users = await model.getUsers(); const book = await model.getBook(editingId);
view.renderUsersOptions(users); view.fillBookForm(book);
} catch (err) { document.getElementById("bookId").value = editingId;
alert(err.message); } catch (error) {
console.error(error);
view.showAlert("Ошибка при загрузке книги", true);
} }
}); }
// Сохранение формы
const bookForm = document.getElementById("bookForm");
if (bookForm) {
bookForm.addEventListener("submit", async (e) => {
e.preventDefault();
try {
const bookData = await view.getBookFormData();
const id = document.getElementById("bookId").value;
if (id) {
await model.updateBook(id, bookData);
view.showAlert("Книга обновлена");
} else {
await model.addBook(bookData);
view.showAlert("Книга добавлена");
}
view.resetBookForm();
document.getElementById("bookId").value = "";
window.location.href = "index.html";
} catch (error) {
console.error(error);
view.showAlert("Ошибка при сохранении книги", true);
}
});
}
// Кнопка "Отмена"
const cancelBtn = document.getElementById("cancelBtn");
if (cancelBtn) {
cancelBtn.addEventListener("click", () => {
window.location.href = "index.html";
});
}
}, },
async loadAuthors() { // Загрузка авторов и статусов для селектов
const authors = await model.getAuthors(); async loadAuthorsAndStatuses() {
view.renderAuthorsOptions(authors); try {
const authors = await model.getAuthors();
const statuses = await model.getStatuses();
const authorSelect = document.getElementById("authorId");
const statusSelect = document.getElementById("statusId");
if (authorSelect) view.renderAuthorOptions(authors, authorSelect);
if (statusSelect) view.renderStatusOptions(statuses, statusSelect);
} catch (error) {
console.error(error);
view.showAlert("Ошибка при загрузке авторов или статусов", true);
}
}, },
async loadBooks() { // Загрузка и рендер книг на главной
const books = await model.getBooks(); async loadAndRenderBooks() {
view.renderBooks(books); try {
}, const books = await model.getBooks();
const authors = await model.getAuthors();
const statuses = await model.getStatuses();
async handleAddBook(bookData) { const booksWithDetails = books.map((book) => {
await model.addBook(bookData); const author = authors.find((a) => String(a.id) === String(book.authorId));
await this.loadBooks(); const status = statuses.find((s) => String(s.id) === String(book.statusId));
view.resetAddBookForm(); return {
}, ...book,
authorName: author ? author.name : "Неизвестен",
statusName: status ? status.name : "Неизвестен",
};
});
async handleOpenAddReaderModal(bookId) { const booksContainer = document.getElementById("booksContainer");
const users = await model.getUsers(); if (booksContainer) view.renderBooks(booksWithDetails, booksContainer);
view.renderUsersOptions(users); } catch (error) {
view.setCurrentBookId(bookId); console.error(error);
}, view.showAlert("Ошибка при загрузке книг", true);
}
async handleAddReader(userId) {
const bookId = view.getCurrentBookId();
await model.addReaderToBook(userId, bookId);
// Можно обновить UI, например, обновить список читателей
view.hideAddReaderModal();
}, },
}; };

View File

@@ -1,43 +1,39 @@
import api from "./api/client"; import { getAllItems, getItem, createItem, updateItem, deleteItem } from "./api/client";
const model = { export default {
// Книги
getBooks() { getBooks() {
return api.getAll("books"); return getAllItems("books");
},
getBook(id) {
return getItem("books", id);
}, },
addBook(book) { addBook(book) {
return api.create("books", book); return createItem("books", book);
}, },
updateBook(id, data) { updateBook(id, data) {
return api.update("books", id, data); return updateItem("books", id, data);
},
deleteBook(id) {
return deleteItem("books", id);
}, },
// Авторы
getAuthors() { getAuthors() {
return api.getAll("authors"); return getAllItems("authors");
}, },
getUsers() { // Статусы
return api.getAll("users"); getStatuses() {
return getAllItems("statuses");
}, },
addStatus(status) {
addUser(user) { return createItem("statuses", status);
return api.create("users", user);
}, },
updateStatus(id, data) {
updateUser(id, data) { return updateItem("statuses", id, data);
return api.update("users", id, data);
}, },
deleteStatus(id) {
addReaderToBook(userId, bookId) { return deleteItem("statuses", id);
return api.getById("users", userId).then((user) => {
const readBooks = user.readBooks || [];
if (!readBooks.includes(bookId)) {
readBooks.push(bookId);
}
return api.update("users", userId, { readBooks });
});
}, },
}; };
export default model;

View File

@@ -1,5 +1,6 @@
const view = { const view = {
renderAuthorOptions(authors, selectElement) { renderAuthorOptions(authors, selectElement) {
selectElement.innerHTML = '<option value="">Выберите автора</option>';
authors.forEach((author) => { authors.forEach((author) => {
const option = document.createElement("option"); const option = document.createElement("option");
option.value = author.id; option.value = author.id;
@@ -8,32 +9,121 @@ const view = {
}); });
}, },
renderUsersOptions(users) { renderStatusOptions(statuses, selectElement) {
const userSelect = document.getElementById("userSelect"); selectElement.innerHTML = '<option value="">Выберите статус</option>';
userSelect.innerHTML = '<option value="">Выберите...</option>'; statuses.forEach((status) => {
users.forEach((user) => {
const option = document.createElement("option"); const option = document.createElement("option");
option.value = user.id; option.value = status.id;
option.textContent = user.number; // или user.name option.textContent = status.name;
userSelect.appendChild(option); selectElement.appendChild(option);
}); });
}, },
getBookFormData() { renderBooks(books, container) {
container.innerHTML = "";
books.forEach((book) => {
const div = document.createElement("div");
div.className = "card mb-3";
div.innerHTML = `
<a href = "manga.html">
<img src="${book.cover || "img/заглушка.jpg"}" class="card-img-top" alt="Обложка книги" style="max-height:300px; object-fit:cover; background:#37c6c6;">
</a>
<div class="card-body ">
<h5 class="card-title">${book.title}</h5>
<p class="card-text">${book.description}</p>
<p class="card-text"><small class="text-muted">Автор: ${book.authorName || "Неизвестен"}</small></p>
<p class="card-text"><small class="text-muted">Статус: ${book.statusName || "Неизвестен"}</small></p>
<button data-id="${book.id}" class="btn btn-danger btn-sm delete-btn">Удалить</button>
<a href="add_book.html?id=${book.id}" class="btn btn-warning btn-sm edit-btn">Редактировать</a>
</div>`;
container.appendChild(div);
});
},
bindCoverInput() {
const coverInput = document.getElementById("cover");
const coverPreview = document.getElementById("coverPreview");
coverInput.addEventListener("change", () => {
const file = coverInput.files[0];
if (!file) {
coverPreview.style.display = "none";
coverPreview.src = "";
return;
}
const reader = new FileReader();
reader.onload = (e) => {
coverPreview.src = e.target.result;
coverPreview.style.display = "block";
};
reader.readAsDataURL(file);
});
},
async getBookFormData() {
const coverInput = document.getElementById("cover");
let coverData = "";
if (coverInput.files.length > 0) {
coverData = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(new Error("Ошибка чтения файла обложки"));
reader.readAsDataURL(coverInput.files[0]);
});
} else {
// Если форма редактирования, и файл не поменяли, можно брать существующий cover (или пустую строку)
coverData = document.getElementById("cover").dataset.currentCover || "";
}
return { return {
title: document.getElementById("title").value.trim(), title: document.getElementById("title").value.trim(),
description: document.getElementById("description").value.trim(), description: document.getElementById("description").value.trim(),
authorId: parseInt(document.getElementById("authorId").value, 10), authorId: parseInt(document.getElementById("authorId").value, 10),
cover: document.getElementById("cover").value.trim(), statusId: parseInt(document.getElementById("statusId").value, 10),
cover: coverData,
}; };
}, },
fillBookForm(book) {
document.getElementById("title").value = book.title || "";
document.getElementById("description").value = book.description || "";
document.getElementById("authorId").value = book.authorId || "";
document.getElementById("statusId").value = book.statusId || "";
// Запоминаем текущую обложку в дата-атрибут для использования при сохранении
const coverInput = document.getElementById("cover");
coverInput.value = ""; // Очистить input файл
coverInput.dataset.currentCover = book.cover || "";
// Показываем превью
const coverPreview = document.getElementById("coverPreview");
if (book.cover) {
coverPreview.src = book.cover;
coverPreview.style.display = "block";
} else {
coverPreview.style.display = "none";
}
},
resetBookForm() { resetBookForm() {
document.getElementById("bookForm").reset(); document.getElementById("bookForm").reset();
const coverPreview = document.getElementById("coverPreview");
coverPreview.style.display = "none";
coverPreview.src = "";
}, },
showAlert(message, isError = false) { showAlert(message, isError = false) {
alert(isError ? ` ${message}` : ` ${message}`); alert(isError ? `Ошибка: ${message}` : `Успех: ${message}`);
},
confirm(message) {
return new Promise((resolve) => {
const result = confirm(message);
resolve(result);
});
}, },
}; };
export default view; export default view;

View File

@@ -5,10 +5,13 @@ import Inputmask from "inputmask";
import controller from "../mvc/controller"; import controller from "../mvc/controller";
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
controller.init(); const phoneInput = document.getElementById("number");
}); if (phoneInput) {
Inputmask("+7 (999) 999-99-99").mask(phoneInput);
// маска для телефона }
document.addEventListener("DOMContentLoaded", () => { if (document.getElementById("booksContainer")) {
Inputmask("+7 (999) 999-99-99").mask(document.getElementById("number")); controller.initIndexPage();
} else if (document.getElementById("bookForm")) {
controller.initAddBookPage();
}
}); });