Скриншот архива с проектом

Задание
Разработать приложение для книжного магазина Книгочей для учета книг, продаж и статистики. Рекомендуемый стек - Python Django DRF Vue.js. Приложение должно иметь удобный и привлекательный интерфейс, дашборд-панель с аналитикой.
Функционал программы
- Учет книг
- Добавление книг
- Редактирование книг
- Учет продаж
- Добавление продаж
- Редактирование продаж
- Аналитическая дашборд панель
- Печать данных продаж
Содержание отчета к программе
- Введение
- Задание на курсовую работу
- Постановка задачи
- Проектная часть
- Теоретическая часть
- Общее описание разработки
- Таблица свойств объектов
- Список идентификаторов
- Структура приложения
- Функциональное описание приложения
- Описание работы программы с представлением экранных форм
- Заключение
- Список использованных источников
- Приложение. Исходный код программы с комментариями
- MainForm.cs
- PlayForm.cs
- RulesForm.cs
- LastGamesForm.cs
Фрагмент программного кода (модели)
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.core.exceptions import ValidationError
from decimal import Decimal
# Create your models here.
class Book(models.Model):
objects = models.Manager()
author = models.CharField(max_length=100, verbose_name="Автор книги")
name = models.CharField(max_length=100, verbose_name="Наименование книги")
description = models.TextField(verbose_name="Описание книги")
GENRE_CHOICES = [
('Ужасы', 'Ужасы'),
('Мистика', 'Мистика'),
('Фантастика', 'Фантастика'),
('Триллер', 'Триллер'),
]
genre = models.CharField(max_length=50, choices=GENRE_CHOICES, verbose_name='Жанр книги', null=True, blank=True)
price = models.DecimalField(verbose_name="Цена книги", max_digits=10,
decimal_places=2)
photo = models.ImageField(upload_to='uploads/images',
verbose_name="Фото книги",
null=True, blank=True)
def __str__(self):
return self.name
class Meta:
verbose_name = 'Книга'
verbose_name_plural = 'Книги'
class Sale(models.Model):
objects = models.Manager()
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='sales', verbose_name='Проданная книга')
date = models.DateField(verbose_name='Дата продажи')
start_quantity = models.PositiveIntegerField(verbose_name="Начальное количество")
end_quantity = models.PositiveIntegerField(verbose_name="Конечное количество")
class Meta:
verbose_name = 'Продажа'
verbose_name_plural = 'Продажи'
def save(self, *args, **kwargs):
if self.end_quantity < self.start_quantity:
raise ValidationError("Конечные продажи должны быть больше или равны начальному количеству.")
super().save(*args, **kwargs)
def get_sold_quantity(self):
"""Количество проданных единиц"""
return int(self.end_quantity) - int(self.start_quantity)
def __str__(self):
return f"{self.book.name}: {self.date} - {self.get_sold_quantity()} шт"
Фрагмент программного кода (компонент Books)
<script setup>
import { ref, onMounted } from "vue";
import axios from "axios";
const books = ref([]);
const searchQuery = ref("");
const viewMode = ref("cards");
const fetchBooks = async (query = "") => {
try {
const url = query
? `http://localhost:8000/api/books/?search=${encodeURIComponent(query)}`
: "http://localhost:8000/api/books/";
const response = await axios.get(url);
books.value = response.data.results || response.data;
} catch (error) {
console.error("Ошибка при загрузке книг:", error);
}
};
onMounted(() => {
fetchBooks();
});
const searchBooks = () => {
fetchBooks(searchQuery.value);
};
const clearSearch = () => {
searchQuery.value = "";
fetchBooks("");
};
</script>
<template>
<div class="container my-4">
<h1 class="mb-4">📚 Список книг</h1>
<!-- кнопки -->
<div class="mb-3 d-flex gap-2">
<router-link to="/books/add" class="btn btn-success">
➕ Добавить книгу
</router-link>
<router-link to="/" class="btn btn-secondary">
← На главную
</router-link>
</div>
<!-- поиск с кнопкой очистки -->
<div class="mb-3 search-container">
<div class="search-wrapper">
<input
v-model="searchQuery"
@keyup.enter="searchBooks"
type="text"
class="form-control search-input"
placeholder="Поиск по названию или автору"
/>
<button
v-if="searchQuery"
@click="clearSearch"
class="btn-clear"
title="Очистить поиск"
>
✕
</button>
<button @click="searchBooks" class="btn btn-primary search-btn">
🔍 Поиск
</button>
</div>
</div>
<!-- переключатель -->
<div class="mb-3">
<button
class="btn btn-outline-dark me-2"
:class="{ active: viewMode === 'cards' }"
@click="viewMode = 'cards'"
>
🃏 Карточки
</button>
<button
class="btn btn-outline-dark"
:class="{ active: viewMode === 'table' }"
@click="viewMode = 'table'"
>
📊 Таблица
</button>
</div>
<!-- ================= TABLE ================= -->
<table
v-if="viewMode === 'table'"
class="table table-hover table-striped align-middle"
>
<thead class="table-info">
<tr>
<th class="text-center">ID</th>
<th>Обложка</th>
<th>Название</th>
<th>Автор</th>
<th>Жанр</th>
<th>Цена</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<tr v-for="book in books" :key="book.id">
<td class="text-center">{{ book.id }}</td>
<td>
<img
v-if="book.photo"
: src="/images/loading-150-1.gif" data-src="book.photo"
class="book-thumb"
:alt="book.name"
/>
<span v-else class="text-muted">Нет фото</span>
</td>
<td class="fw-bold">{{ book.name }}</td>
<td>{{ book.author }}</td>
<td>{{ book.genre || '—' }}</td>
<td class="fw-bold text-success">{{ book.price }} ₽</td>
<td>
<router-link
:to="`/books/${book.id}`"
class="btn btn-primary btn-sm me-1"
>
👁️ Подробнее
</router-link>
<router-link
:to="`/books/${book.id}/edit`"
class="btn btn-warning btn-sm"
>
✏️ Изменить
</router-link>
</td>
</tr>
</tbody>
</table>
<!-- ================= CARDS ================= -->
<div v-else class="row">
<div
v-for="book in books"
:key="book.id"
class="col-md-6 col-lg-4 mb-4"
>
<div class="card h-100 book-card">
<div class="card-img-top-wrapper">
<img
v-if="book.photo"
: src="/images/loading-150-1.gif" data-src="book.photo"
class="card-img-top book-image"
:alt="book.name"
/>
<div v-else class="no-image-placeholder">
<span>📖</span>
<p>Нет обложки</p>
</div>
</div>
<div class="card-body d-flex flex-column">
<h5 class="card-title">{{ book.name }}</h5>
<p class="card-text text-muted small">{{ book.author }}</p>
<p class="card-text description-text">
{{ book.description?.slice(0, 100) }}...
</p>
<div class="mt-2 mb-2">
<span class="badge genre-badge">{{ book.genre || 'Без жанра' }}</span>
</div>
<div class="price-tag">
{{ book.price }} ₽
</div>
<div class="card-actions mt-3">
<router-link
:to="`/books/${book.id}`"
class="btn btn-outline-primary btn-sm"
>
📖 Подробнее
</router-link>
<router-link
:to="`/books/${book.id}/edit`"
class="btn btn-outline-warning btn-sm"
>
✏️ Редактировать
</router-link>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.container {
max-width: 1300px;
margin: 0 auto;
}
/* Стили для поиска */
.search-container {
position: relative;
}
.search-wrapper {
display: flex;
position: relative;
gap: 10px;
align-items: center;
}
.search-input {
flex: 1;
padding-right: 45px;
}
.btn-clear {
position: absolute;
right: 115px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: #999;
cursor: pointer;
font-size: 18px;
padding: 5px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
width: 28px;
height: 28px;
transition: all 0.2s;
z-index: 1;
}
.btn-clear:hover {
background: #e0e0e0;
color: #333;
}
.search-btn {
white-space: nowrap;
}
/* Карточки */
.book-card {
border-radius: 16px;
overflow: hidden;
transition: all 0.3s ease;
border: 1px solid #e9ecef;
background: white;
}
.book-card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
}
.card-img-top-wrapper {
background: #f8f9fa;
padding: 20px;
text-align: center;
border-bottom: 1px solid #e9ecef;
}
.book-image {
max-width: 100%;
height: 200px;
object-fit: contain;
border-radius: 8px;
}
.no-image-placeholder {
height: 200px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #f8f9fa;
color: #adb5bd;
}
.no-image-placeholder span {
font-size: 48px;
margin-bottom: 8px;
}
.no-image-placeholder p {
margin: 0;
font-size: 14px;
}
.card-body {
padding: 20px;
}
.card-title {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 8px;
color: #333;
line-height: 1.3;
}
.description-text {
color: #6c757d;
font-size: 0.85rem;
line-height: 1.4;
margin-bottom: 12px;
flex-grow: 1;
}
.genre-badge {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 6px 12px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 500;
}
.price-tag {
font-size: 1.5rem;
font-weight: 700;
color: #28a745;
margin: 12px 0;
}
.card-actions {
display: flex;
gap: 10px;
margin-top: auto;
}
.card-actions .btn {
flex: 1;
padding: 8px 12px;
font-size: 0.85rem;
border-radius: 8px;
transition: all 0.2s;
}
.btn-outline-primary {
border: 2px solid #667eea;
color: #667eea;
background: white;
}
.btn-outline-primary:hover {
background: #667eea;
color: white;
transform: translateY(-2px);
}
.btn-outline-warning {
border: 2px solid #ffc107;
color: #856404;
background: white;
}
.btn-outline-warning:hover {
background: #ffc107;
color: white;
transform: translateY(-2px);
}
.book-thumb {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 8px;
}
.table {
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}
.table thead th {
background: #f8f9fa;
border-bottom: 2px solid #dee2e6;
font-weight: 600;
}
.table tbody tr:hover {
background: #f8f9ff;
}
.btn.active {
background-color: #667eea;
color: white;
border-color: #667eea;
}
@media (max-width: 768px) {
.card-actions {
flex-direction: column;
}
.book-image {
height: 150px;
}
.price-tag {
font-size: 1.2rem;
}
.search-wrapper {
flex-wrap: wrap;
}
.search-input {
width: 100%;
}
.btn-clear {
right: 20px;
}
.search-btn {
width: 100%;
}
}
</style>
Пояснения по запуску программы
1. Скачиваем Visual Studio Code/ 2. Настраиваем расширения для работы с Python и Vue. 3. Скачиваем интерпретатор Python, при установке незабываем ставить галочку PATH. 4. Скачиваем и устанавливаем node.js. 5. Открываем проект в Visual Studio - устанавливаем модули Python из файла. 6. В папке Фронтенд - клиент - устанавливаем удаленные иодули node. 7. Создаем два терминала в студии. в одном запускаем бэкенд - cd Backend - cd server - python manage.py runserver. dj втором запускаем клиент - cd Frontend - cd client - npm run dev. Пользуемся приложением. А для входа в админ панель Django потребуется создать права суперпользователя . Но я вам сразу дам данные - Логин: Admin Пароль: E9pJqf9CG.Twx45

Телеграм
-