Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f52ebb496 | |||
| 5408021e1b | |||
| e9d0a1d549 | |||
| 3293c8b1b9 | |||
| e527a79d0c | |||
| 616e8e76c9 | |||
| 67fdf675f4 | |||
| 6bbec3f25f | |||
| d540511a47 | |||
| 7b5012a6e8 | |||
| 78fa452d28 | |||
| f8270722cc |
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
File diff suppressed because one or more lines are too long
@@ -12,4 +12,4 @@
|
|||||||
<div id="root" class="bg-dark"></div>
|
<div id="root" class="bg-dark"></div>
|
||||||
<script type="module" src="/src/main.jsx"></script>
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
101
Lab/src/App.jsx
101
Lab/src/App.jsx
@@ -1,8 +1,13 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Routes, Route } from "react-router-dom";
|
||||||
import Navbar from "./components/Navbar";
|
import Navbar from "./components/Navbar";
|
||||||
import Footer from "./components/Footer";
|
import Footer from "./components/Footer";
|
||||||
import BookForm from "./components/BookForm";
|
import Home from "./components/Home";
|
||||||
import BookList from "./components/BookList";
|
import News from "./components/News";
|
||||||
|
import Manga from "./components/Manga";
|
||||||
|
import Author from "./components/Author";
|
||||||
|
import Reading from "./components/Reading";
|
||||||
|
import Account from "./components/Account";
|
||||||
import data from "../data.json"; // Используем только для авторов и статусов
|
import data from "../data.json"; // Используем только для авторов и статусов
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
@@ -10,8 +15,8 @@ function App() {
|
|||||||
const [authors] = useState(data.authors);
|
const [authors] = useState(data.authors);
|
||||||
const [statuses] = useState(data.statuses);
|
const [statuses] = useState(data.statuses);
|
||||||
const [editingBook, setEditingBook] = useState(null);
|
const [editingBook, setEditingBook] = useState(null);
|
||||||
const [statusFilter, setStatusFilter] = useState(""); // пусто — все книги
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
|
const [statusFilter, setStatusFilter] = useState("");
|
||||||
|
|
||||||
// Загрузка книг с сервера
|
// Загрузка книг с сервера
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -43,6 +48,20 @@ function App() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Массовое удаление
|
||||||
|
const deleteSelectedBooks = (ids) => {
|
||||||
|
Promise.all(
|
||||||
|
ids.map((id) =>
|
||||||
|
fetch(`http://localhost:5174/books/${id}`, { method: "DELETE" })
|
||||||
|
)
|
||||||
|
).then(() => {
|
||||||
|
fetch("http://localhost:5174/books")
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => setBooks(data));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// Обновить книгу
|
// Обновить книгу
|
||||||
const updateBook = (book) => {
|
const updateBook = (book) => {
|
||||||
fetch(`http://localhost:5174/books/${book.id}`, {
|
fetch(`http://localhost:5174/books/${book.id}`, {
|
||||||
@@ -56,53 +75,49 @@ function App() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Фильтрация книг по статусу
|
// Фильтрация книг по поиску и статусу
|
||||||
const filteredBooks = statusFilter
|
const filteredBooks = books.filter(book =>
|
||||||
? books.filter(
|
book.title.toLowerCase().includes(searchQuery.toLowerCase()) &&
|
||||||
book =>
|
(statusFilter === "" ||
|
||||||
statuses.find(s => String(s.id) === String(book.statusId))?.name === statusFilter
|
statuses.find(s => String(s.id) === String(book.statusId))?.name === statusFilter)
|
||||||
)
|
);
|
||||||
: books;
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main className="container py-4">
|
<main className="container py-4">
|
||||||
<h1 className="mb-4">Книги</h1>
|
<Routes>
|
||||||
{/* --- Фильтр по статусу --- */}
|
<Route
|
||||||
<div className="mb-3" style={{ maxWidth: 350 }}>
|
path="/"
|
||||||
<select
|
element={
|
||||||
className="form-select"
|
<Home
|
||||||
value={statusFilter}
|
books={filteredBooks}
|
||||||
onChange={e => setStatusFilter(e.target.value)}
|
authors={authors}
|
||||||
>
|
statuses={statuses}
|
||||||
<option value="">Все статусы</option>
|
editingBook={editingBook}
|
||||||
{statuses.map(s => (
|
onAdd={addBook}
|
||||||
<option key={s.id} value={s.name}>{s.name}</option>
|
onEdit={updateBook}
|
||||||
))}
|
onDelete={deleteBook}
|
||||||
</select>
|
onCancel={() => setEditingBook(null)}
|
||||||
</div>
|
setEditingBook={setEditingBook}
|
||||||
{/* --- Форма добавления/редактирования --- */}
|
searchQuery={searchQuery}
|
||||||
<BookForm
|
setSearchQuery={setSearchQuery}
|
||||||
authors={authors}
|
statusFilter={statusFilter}
|
||||||
statuses={statuses}
|
setStatusFilter={setStatusFilter}
|
||||||
onSubmit={editingBook ? updateBook : addBook}
|
onDeleteSelected={deleteSelectedBooks}
|
||||||
editingBook={editingBook}
|
/>
|
||||||
onCancel={() => setEditingBook(null)}
|
}
|
||||||
/>
|
/>
|
||||||
{/* --- Список книг (фильтрованные) --- */}
|
<Route path="/news" element={<News />} />
|
||||||
<BookList
|
<Route path="/manga/:id" element={<Manga />} />
|
||||||
books={filteredBooks}
|
<Route path="/author/:id" element={<Author />} />
|
||||||
authors={authors}
|
<Route path="/reading/:id" element={<Reading />} />
|
||||||
statuses={statuses}
|
<Route path="/account" element={<Account />} />
|
||||||
onEdit={setEditingBook}
|
</Routes>
|
||||||
onDelete={deleteBook}
|
|
||||||
/>
|
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
70
Lab/src/components/Account.jsx
Normal file
70
Lab/src/components/Account.jsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
export default function Account() {
|
||||||
|
const [phone, setPhone] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [submitted, setSubmitted] = useState(false);
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setSubmitted(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container my-5">
|
||||||
|
<div className="row justify-content-center">
|
||||||
|
<div className="col-md-6 bg-secondary p-4 rounded bg-custom-dark">
|
||||||
|
<form id="loginForm" onSubmit={handleSubmit}>
|
||||||
|
<h2 className="text-center mb-4">
|
||||||
|
<i className="bi bi-person-circle me-2"></i>Вход в систему
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="number" className="form-label">
|
||||||
|
<i className="bi bi-telephone-fill me-2"></i>Номер телефона
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
className="form-control"
|
||||||
|
id="number"
|
||||||
|
name="number_acc"
|
||||||
|
placeholder="+7 (___) ___-__-__"
|
||||||
|
required
|
||||||
|
value={phone}
|
||||||
|
onChange={e => setPhone(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<label htmlFor="password" className="form-label">
|
||||||
|
<i className="bi bi-lock-fill me-2"></i>Пароль
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
className="form-control"
|
||||||
|
id="password"
|
||||||
|
name="password_acc"
|
||||||
|
placeholder="Пароль"
|
||||||
|
required
|
||||||
|
minLength="4"
|
||||||
|
value={password}
|
||||||
|
onChange={e => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="d-grid">
|
||||||
|
<button type="submit" className="btn btn-warning">
|
||||||
|
<i className="bi bi-box-arrow-in-right me-2"></i>Войти
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{submitted && (
|
||||||
|
<div className="mt-3 alert alert-info p-2">
|
||||||
|
<i className="bi bi-info-circle me-2"></i>Проверка формы: пока без логики!
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
Lab/src/components/Author.jsx
Normal file
24
Lab/src/components/Author.jsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function Author() {
|
||||||
|
return (
|
||||||
|
<div className="container mt-5">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-4 text-center">
|
||||||
|
<img
|
||||||
|
className="img-fluid rounded"
|
||||||
|
src="../img/ХаяоМиядзаки.png" // Используй нужную тебе картинку!
|
||||||
|
alt="Суи Исида"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-md-8 text-color-light">
|
||||||
|
<h3>Автор: Фусэ </h3>
|
||||||
|
<p>
|
||||||
|
Известен произведением «О моём перерождении в слизь».
|
||||||
|
В нём автор объединяет элементы западных и восточных ролевых игр и использует идею жанра «исекай» о перерождении или призыве в другой мир.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,24 +1,64 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import StatusLabel from "./StatusLabel";
|
import StatusLabel from "./StatusLabel";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
function BookCard({ book, authorName, statusName, onEdit, onDelete }) {
|
function BookCard({
|
||||||
|
book,
|
||||||
|
authorName,
|
||||||
|
statusName,
|
||||||
|
onEdit,
|
||||||
|
onDelete,
|
||||||
|
selected,
|
||||||
|
onSelect
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="card mb-3 w-100" style={{ maxWidth: "450px", margin: "0 auto" }}>
|
<div
|
||||||
<img
|
className="card card-hover mb-3 w-100"
|
||||||
src={book.cover || "https://placehold.co/200x300"}
|
style={{ maxWidth: "450px", margin: "0 auto", position: "relative" }}
|
||||||
className="card-img-top shadow mt-2 mb-3"
|
>
|
||||||
alt="Обложка книги"
|
{/* Чекбокс для выделения */}
|
||||||
style={{ height: "300px", objectFit: "contain" }}
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selected}
|
||||||
|
onChange={e => onSelect(book.id, e.target.checked)}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 10,
|
||||||
|
left: 10,
|
||||||
|
zIndex: 10,
|
||||||
|
width: "20px",
|
||||||
|
height: "20px"
|
||||||
|
}}
|
||||||
|
title="Выделить для массового удаления"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Link to={`/manga/${book.id}`}>
|
||||||
|
<img
|
||||||
|
src={book.cover || "https://placehold.co/200x300"}
|
||||||
|
className="card-img-top shadow mt-2 mb-3"
|
||||||
|
alt="Обложка книги"
|
||||||
|
style={{ maxHeight: "300px", objectFit: "contain" }}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<h5 className="card-title">{book.title}</h5>
|
<h5 className="card-title">{book.title}</h5>
|
||||||
<p className="card-text">{book.description}</p>
|
<p className="card-text">{book.description}</p>
|
||||||
<p className="card-text"><small>Автор: {authorName}</small></p>
|
<p className="card-text">
|
||||||
<p className="card-text"><StatusLabel statusName={statusName} /></p>
|
<small>Автор: {authorName}</small>
|
||||||
<button className="btn btn-warning me-2" onClick={() => onEdit(book)}>
|
</p>
|
||||||
|
<p className="card-text">
|
||||||
|
<StatusLabel statusName={statusName} />
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
className="btn btn-warning me-2"
|
||||||
|
onClick={() => onEdit(book)}
|
||||||
|
>
|
||||||
Редактировать
|
Редактировать
|
||||||
</button>
|
</button>
|
||||||
<button className="btn btn-danger" onClick={() => onDelete(book.id)}>
|
<button
|
||||||
|
className="btn btn-danger"
|
||||||
|
onClick={() => onDelete(book.id)}
|
||||||
|
>
|
||||||
Удалить
|
Удалить
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,23 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import BookCard from "./BookCard";
|
import BookCard from "./BookCard";
|
||||||
|
|
||||||
function BookList({ books, authors, statuses, onEdit, onDelete }) {
|
function BookList({
|
||||||
const getAuthorName = (id) => authors.find(a => String(a.id) === String(id))?.name || "Неизвестен";
|
books,
|
||||||
const getStatusName = (id) => statuses.find(s => String(s.id) === String(id))?.name || "Нет статуса";
|
authors,
|
||||||
|
statuses,
|
||||||
|
onEdit,
|
||||||
|
onDelete,
|
||||||
|
selectedIds,
|
||||||
|
onSelect
|
||||||
|
}) {
|
||||||
|
const getAuthorName = (id) =>
|
||||||
|
authors.find((a) => String(a.id) === String(id))?.name || "Неизвестен";
|
||||||
|
const getStatusName = (id) =>
|
||||||
|
statuses.find((s) => String(s.id) === String(id))?.name || "Нет статуса";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row justify-content-center ">
|
<div className="row justify-content-center ">
|
||||||
{books.map(book => (
|
{books.map((book) => (
|
||||||
<div
|
<div
|
||||||
className="col-12 col-sm-10 col-md-6 col-lg-4 d-flex align-items-stretch mb-4"
|
className="col-12 col-sm-10 col-md-6 col-lg-4 d-flex align-items-stretch mb-4"
|
||||||
key={book.id}
|
key={book.id}
|
||||||
@@ -18,6 +28,8 @@ function BookList({ books, authors, statuses, onEdit, onDelete }) {
|
|||||||
statusName={getStatusName(book.statusId)}
|
statusName={getStatusName(book.statusId)}
|
||||||
onEdit={onEdit}
|
onEdit={onEdit}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
|
selected={selectedIds.includes(book.id)}
|
||||||
|
onSelect={onSelect}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
94
Lab/src/components/Home.jsx
Normal file
94
Lab/src/components/Home.jsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import BookForm from "./BookForm";
|
||||||
|
import BookList from "./BookList";
|
||||||
|
|
||||||
|
export default function Home({
|
||||||
|
books,
|
||||||
|
authors,
|
||||||
|
statuses,
|
||||||
|
editingBook,
|
||||||
|
onAdd,
|
||||||
|
onEdit,
|
||||||
|
onDelete,
|
||||||
|
onCancel,
|
||||||
|
setEditingBook,
|
||||||
|
searchQuery,
|
||||||
|
setSearchQuery,
|
||||||
|
statusFilter,
|
||||||
|
setStatusFilter,
|
||||||
|
onDeleteSelected // новый пропс!
|
||||||
|
}) {
|
||||||
|
// Состояние для выделенных книг
|
||||||
|
const [selectedIds, setSelectedIds] = useState([]);
|
||||||
|
|
||||||
|
// Обработчик выделения карточки
|
||||||
|
const handleSelect = (id, checked) => {
|
||||||
|
setSelectedIds(prev =>
|
||||||
|
checked ? [...prev, id] : prev.filter(selectedId => selectedId !== id)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Кнопка массового удаления
|
||||||
|
const handleDeleteSelected = () => {
|
||||||
|
if (
|
||||||
|
selectedIds.length &&
|
||||||
|
window.confirm(`Удалить ${selectedIds.length} выделенных книг?`)
|
||||||
|
) {
|
||||||
|
onDeleteSelected(selectedIds);
|
||||||
|
setSelectedIds([]); // очистка выделения
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1 className="mb-4">Книги</h1>
|
||||||
|
<div className="mb-3" style={{ maxWidth: 350 }}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control mb-2"
|
||||||
|
placeholder="Поиск по названию"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={e => setSearchQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
<select
|
||||||
|
className="form-select"
|
||||||
|
value={statusFilter}
|
||||||
|
onChange={e => setStatusFilter(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">Все статусы</option>
|
||||||
|
{statuses.map(s => (
|
||||||
|
<option key={s.id} value={s.name}>{s.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Кнопка массового удаления */}
|
||||||
|
<div className="mb-2">
|
||||||
|
<button
|
||||||
|
className="btn btn-danger"
|
||||||
|
disabled={selectedIds.length === 0}
|
||||||
|
onClick={handleDeleteSelected}
|
||||||
|
>
|
||||||
|
Удалить выделенные ({selectedIds.length})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BookForm
|
||||||
|
authors={authors}
|
||||||
|
statuses={statuses}
|
||||||
|
onSubmit={editingBook ? onEdit : onAdd}
|
||||||
|
editingBook={editingBook}
|
||||||
|
onCancel={onCancel}
|
||||||
|
/>
|
||||||
|
<BookList
|
||||||
|
books={books}
|
||||||
|
authors={authors}
|
||||||
|
statuses={statuses}
|
||||||
|
onEdit={setEditingBook}
|
||||||
|
onDelete={onDelete}
|
||||||
|
selectedIds={selectedIds}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
37
Lab/src/components/Manga.jsx
Normal file
37
Lab/src/components/Manga.jsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function Manga() {
|
||||||
|
return (
|
||||||
|
<main className="container my-5 flex-grow-1">
|
||||||
|
<div className="row justify-content-center">
|
||||||
|
<div className="col-md-8 text-center">
|
||||||
|
<figure className="figure">
|
||||||
|
<img
|
||||||
|
src="/img/заглушка.jpg"
|
||||||
|
className="figure-img img-fluid rounded"
|
||||||
|
alt="Токийский Гуль"
|
||||||
|
style={{ maxHeight: 350, objectFit: "contain" }}
|
||||||
|
/>
|
||||||
|
<figcaption className="figure-caption text-light">
|
||||||
|
О моём перерождении в слизь
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
<p className="mt-4 text-color-light">
|
||||||
|
Обычный служащий финансовой компании Сатору Миками погибает, защищая коллегу от грабителя с ножом.
|
||||||
|
После смерти Сатору попадает в фэнтезийный мир, в котором он предстаёт в виде комка слизи средних размеров по имени Римуру, наделённой немалым разумом.
|
||||||
|
Отныне Римуру будет жить в мире, полном разных рас, в надежде построить однажды страну, где к каждой расе будут относиться одинаково.
|
||||||
|
</p>
|
||||||
|
<div className="d-flex justify-content-center gap-3 mt-3">
|
||||||
|
<Link to="/author/1" className="btn btn-primary">
|
||||||
|
<i className="bi bi-person-lines-fill me-2"></i>Про автора
|
||||||
|
</Link>
|
||||||
|
<Link to="/reading/1" className="btn btn-success">
|
||||||
|
<i className="bi bi-book me-2"></i>Читать
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ export default function Navbar() {
|
|||||||
<Link className="navbar-brand" to="/">
|
<Link className="navbar-brand" to="/">
|
||||||
<img src="/img/manga.png" alt="ЛОГО" height="50" />
|
<img src="/img/manga.png" alt="ЛОГО" height="50" />
|
||||||
</Link>
|
</Link>
|
||||||
|
{/* Бургер-кнопка для мобилок */}
|
||||||
<button
|
<button
|
||||||
className="navbar-toggler"
|
className="navbar-toggler"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -20,11 +21,18 @@ export default function Navbar() {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="collapse navbar-collapse ms-3" id="navbarNavDropdown">
|
<div className="collapse navbar-collapse ms-3" id="navbarNavDropdown">
|
||||||
|
<ul className="navbar-nav ms-auto mb-2 mb-lg-0">
|
||||||
|
<li className="nav-item me-2">
|
||||||
<Link to="/account" className="btn btn-outline-warning">
|
<Link to="/news" className="btn btn-outline-warning">
|
||||||
Вход
|
Новости
|
||||||
</Link>
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<Link to="/account" className="btn btn-outline-warning">
|
||||||
|
Вход
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
|
|||||||
14
Lab/src/components/News.jsx
Normal file
14
Lab/src/components/News.jsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export default function News() {
|
||||||
|
return (
|
||||||
|
<div className="container-news mt-4">
|
||||||
|
<div className="card">
|
||||||
|
<img src="/img/новость.jpg" className="card-img-top" alt="Новость" />
|
||||||
|
<div className="card-body">
|
||||||
|
<h5 className="card-title">Новость</h5>
|
||||||
|
<p className="card-text">Lorem ipsum dolor sit amet...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
33
Lab/src/components/Reading.jsx
Normal file
33
Lab/src/components/Reading.jsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function Reading() {
|
||||||
|
|
||||||
|
|
||||||
|
// Пример массив страниц — можешь заменить на свой массив или получать из API:
|
||||||
|
const pages = [
|
||||||
|
"/img/SL1.png",
|
||||||
|
"/img/SL2.png",
|
||||||
|
// Можно добавить другие страницы
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mt-5 flex-grow-1">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-12 text-center">
|
||||||
|
<h3>Читать мангу...</h3>
|
||||||
|
<div className="d-flex justify-content-center">
|
||||||
|
{pages.map((src, idx) => (
|
||||||
|
<img
|
||||||
|
className="img-reading mx-2"
|
||||||
|
src={src}
|
||||||
|
alt={`Манга страница ${idx + 1}`}
|
||||||
|
key={src}
|
||||||
|
style={{ maxWidth: 350, maxHeight: 500 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +1,12 @@
|
|||||||
|
.card.card-hover {
|
||||||
|
transition: transform 0.2s cubic-bezier(.4,2,.3,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card.card-hover:hover {
|
||||||
|
transform: scale(1.04);
|
||||||
|
z-index: 2;
|
||||||
|
box-shadow: 0 8px 32px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
.bg-custom-dark {
|
.bg-custom-dark {
|
||||||
background-color: #0f0630 !important;
|
background-color: #0f0630 !important;
|
||||||
@@ -49,4 +58,8 @@ footer {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-color-light {
|
||||||
|
color: aliceblue;
|
||||||
}
|
}
|
||||||
5672
package-lock.json
generated
5672
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "int-prog",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"start": "npm-run-all --parallel backend vite",
|
|
||||||
"vite": "vite",
|
|
||||||
"build": "vite build",
|
|
||||||
"serve": "http-server -p 3000 ./html/",
|
|
||||||
"backend": "json-server ./html/database/data.json -p 5174",
|
|
||||||
"prod": "npm-run-all build serve --parallel backend serve",
|
|
||||||
"lint": "eslint . --ext js --report-unused-disable-directives --max-warnings 0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"bootstrap": "5.3.3",
|
|
||||||
"bootstrap-icons": "^1.11.3",
|
|
||||||
"inputmask": "^5.0.9"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"eslint": "8.56.0",
|
|
||||||
"eslint-config-airbnb-base": "15.0.0",
|
|
||||||
"eslint-config-prettier": "10.0.2",
|
|
||||||
"eslint-plugin-html": "8.1.2",
|
|
||||||
"eslint-plugin-import": "2.31.0",
|
|
||||||
"eslint-plugin-prettier": "5.2.3",
|
|
||||||
"http-server": "14.1.1",
|
|
||||||
"json-server": "^1.0.0-beta.3",
|
|
||||||
"npm-run-all": "4.1.5",
|
|
||||||
"vite": "6.2.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
Отчет №6.docx
Normal file
BIN
Отчет №6.docx
Normal file
Binary file not shown.
Reference in New Issue
Block a user