Добавил погинацию (бесконечную) на страницу "Проекты"

This commit is contained in:
NikDizell 2026-02-23 23:17:36 +03:00
parent c677d6ee8c
commit cf5fa86590
6 changed files with 206 additions and 72 deletions

View File

@ -0,0 +1,50 @@
(function() {
let currentPage = window.currentPage || 1;
let totalPages = window.totalPages || 1;
let loading = false;
const container = document.getElementById('projects-container');
const spinner = document.getElementById('loading-spinner');
if (!container || currentPage >= totalPages) return;
function shouldLoad() {
if (loading) return false;
const scrollPosition = window.scrollY + window.innerHeight;
const threshold = document.documentElement.scrollHeight - 300;
return scrollPosition >= threshold;
}
function loadNextPage() {
if (!shouldLoad()) return;
loading = true;
if (spinner) spinner.style.display = 'block';
const nextPage = currentPage + 1;
fetch(`?page=${nextPage}`, {
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(response => response.json())
.then(data => {
if (data.html) {
container.insertAdjacentHTML('beforeend', data.html);
currentPage = nextPage;
}
if (spinner) spinner.style.display = 'none';
loading = false;
if (currentPage >= totalPages) spinner?.remove();
})
.catch(error => {
console.error('Ошибка загрузки:', error);
if (spinner) spinner.style.display = 'none';
loading = false;
});
}
let timer;
window.addEventListener('scroll', () => {
clearTimeout(timer);
timer = setTimeout(loadNextPage, 150);
});
window.addEventListener('load', loadNextPage);
})();

View File

@ -0,0 +1,56 @@
<li class="modern-card fade-in">
<div class="content-card">
<h2>{{post.title}}</h2>
<!-- Добавляем микроразметку для проекта -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CreativeWork",
"name": "{{ post.title }}",
"description": "{{ p.description|striptags|truncatewords:30 }}",
"author": {
"@type": "Person",
"name": "Николай Сердюк"
},
"datePublished": "{{ post.time_create|date:'Y-m-d' }}"
}
</script>
<div class="solution-accordion">
<div class="accordion-item">
<div class="accordion-header" onclick="toggleAccordion(this)">
<strong>📋 Описание задачи</strong>
<span class="accordion-icon"></span>
</div>
<div class="accordion-content">
{{post.description}}
</div>
</div>
<div class="accordion-item">
<div class="accordion-header" onclick="toggleAccordion(this)">
<strong>🔧 Описание решения</strong>
<span class="accordion-icon"></span>
</div>
<div class="accordion-content">
{{post.implementation}}
</div>
</div>
<div class="accordion-item">
<div class="accordion-header" onclick="toggleAccordion(this)">
<strong>✅ Результат</strong>
<span class="accordion-icon"></span>
</div>
<div class="accordion-content">
{{post.closing}}
</div>
</div>
</div>
<div class="article-panel">
<p class="first">Опубликовано: {{post.time_create|date:"d.m.Y"}}</p>
</div>
</div>
</li>

View File

@ -0,0 +1,5 @@
{% autoescape off %}
{% for post in posts %}
{% include 'programmer/includes/project_card.html' %}
{% endfor %}
{% endautoescape %}

View File

@ -13,75 +13,9 @@
<p class="page-subtitle">Реализованные решения и кейсы по автоматизации бизнес-процессов</p> <p class="page-subtitle">Реализованные решения и кейсы по автоматизации бизнес-процессов</p>
</div> </div>
<div class="improved-list"> <ul class="improved-list" id="projects-container">
{% autoescape off %} {% include 'programmer/includes/project_cards.html' %}
{% for p in posts %} </ul>
<li class="modern-card fade-in">
<div class="content-card">
<h2>{{p.title}}</h2>
<!-- Добавляем микроразметку для проекта -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "CreativeWork",
"name": "{{ p.title }}",
"description": "{{ p.description|striptags|truncatewords:30 }}",
"author": {
"@type": "Person",
"name": "Николай Сердюк"
},
"datePublished": "{{ p.time_create|date:'Y-m-d' }}"
}
</script>
<div class="solution-accordion">
<div class="accordion-item">
<div class="accordion-header" onclick="toggleAccordion(this)">
<strong>📋 Описание задачи</strong>
<span class="accordion-icon"></span>
</div>
<div class="accordion-content">
{{p.description}}
</div>
</div>
<div class="accordion-item">
<div class="accordion-header" onclick="toggleAccordion(this)">
<strong>🔧 Описание решения</strong>
<span class="accordion-icon"></span>
</div>
<div class="accordion-content">
{{p.implementation}}
</div>
</div>
<div class="accordion-item">
<div class="accordion-header" onclick="toggleAccordion(this)">
<strong>✅ Результат</strong>
<span class="accordion-icon"></span>
</div>
<div class="accordion-content">
{{p.closing}}
</div>
</div>
</div>
<div class="article-panel">
<p class="first">Опубликовано: {{p.time_create|date:"d.m.Y"}}</p>
</div>
</div>
{% empty %}
<div class="modern-card text-center fade-in">
<h3>🚀 Проекты в разработке</h3>
<p class="card-subtitle">Скоро здесь появятся новые кейсы автоматизации</p>
</div>
</li>
{% endfor %}
{% endautoescape %}
</div>
{% if not posts %} {% if not posts %}
<div class="content-card text-center"> <div class="content-card text-center">
@ -90,7 +24,23 @@
</div> </div>
{% endif %} {% endif %}
<!-- Подключаем внешний скрипт --> <!-- Индикатор загрузки -->
<script src="{% static 'programmer/js/solution-accordion.js' %}"></script> {% if page_obj.has_next %}
<div id="loading-spinner" style="display: none; text-align: center; margin: 20px 0;">
<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Загрузка...</span></div>
<p>Загрузка проектов...</p>
</div>
{% endif %}
<!-- Данные пагинации -->
<script>
window.currentPage = {{ page_obj.number }};
window.totalPages = {{ paginator.num_pages }};
</script>
<script src="{% static 'programmer/js/solution-accordion.js' %}"></script>
{% endblock %}
{% block extra_js %}
<script src="{% static 'programmer/js/infinite_scroll.js' %}"></script>
{% endblock %} {% endblock %}

View File

@ -1,4 +1,4 @@
from django.http import HttpResponse, HttpResponseNotFound, HttpRequest from django.http import HttpResponse, HttpResponseNotFound, HttpRequest, JsonResponse
from .models import * from .models import *
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib import messages from django.contrib import messages
@ -14,6 +14,7 @@ from django.views.generic import TemplateView, ListView
from .mixins import PageViewTrackingMixin, MenuContextMixin from .mixins import PageViewTrackingMixin, MenuContextMixin
from .services import get_published_queryset, track_page_view from .services import get_published_queryset, track_page_view
from typing import Any, Dict, Type from typing import Any, Dict, Type
from django.template.loader import render_to_string
# menu = [ # menu = [
@ -46,6 +47,27 @@ class BaseListView(PageViewTrackingMixin, MenuContextMixin, ListView):
context.update(self.extra_context) context.update(self.extra_context)
return context return context
def render_to_response(self, context, **response_kwargs):
# Если это AJAX-запрос, возвращаем JSON с HTML фрагментом
if self.request.headers.get('x-requested-with') == 'XMLHttpRequest':
# Определяем шаблон для карточек
cards_template = getattr(self, 'cards_template', None)
if not cards_template:
# Генерируем имя по умолчанию: app/includes/model_cards.html
cards_template = f"{self.model._meta.app_label}/includes/{self.model._meta.model_name}_cards.html"
html = render_to_string(
cards_template,
{self.context_object_name: context[self.context_object_name]},
request=self.request
)
return JsonResponse({
'html': html,
'has_next': context['page_obj'].has_next() if context.get('page_obj') else False
})
# Обычный запрос
return super().render_to_response(context, **response_kwargs)
class HomePageView(BasePageView): class HomePageView(BasePageView):
"""Главная страница с формой обратной связи.""" """Главная страница с формой обратной связи."""
@ -101,6 +123,7 @@ class SolutionListView(BaseListView):
template_name = 'programmer/solution.html' template_name = 'programmer/solution.html'
context_object_name = 'posts' context_object_name = 'posts'
paginate_by = 1 # Количество проектов на странице paginate_by = 1 # Количество проектов на странице
cards_template = 'programmer/includes/project_cards.html'
ordering = ['-time_create'] ordering = ['-time_create']
def get_queryset(self) -> QuerySet: def get_queryset(self) -> QuerySet:

View File

@ -0,0 +1,50 @@
(function() {
let currentPage = window.currentPage || 1;
let totalPages = window.totalPages || 1;
let loading = false;
const container = document.getElementById('projects-container');
const spinner = document.getElementById('loading-spinner');
if (!container || currentPage >= totalPages) return;
function shouldLoad() {
if (loading) return false;
const scrollPosition = window.scrollY + window.innerHeight;
const threshold = document.documentElement.scrollHeight - 300;
return scrollPosition >= threshold;
}
function loadNextPage() {
if (!shouldLoad()) return;
loading = true;
if (spinner) spinner.style.display = 'block';
const nextPage = currentPage + 1;
fetch(`?page=${nextPage}`, {
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(response => response.json())
.then(data => {
if (data.html) {
container.insertAdjacentHTML('beforeend', data.html);
currentPage = nextPage;
}
if (spinner) spinner.style.display = 'none';
loading = false;
if (currentPage >= totalPages) spinner?.remove();
})
.catch(error => {
console.error('Ошибка загрузки:', error);
if (spinner) spinner.style.display = 'none';
loading = false;
});
}
let timer;
window.addEventListener('scroll', () => {
clearTimeout(timer);
timer = setTimeout(loadNextPage, 150);
});
window.addEventListener('load', loadNextPage);
})();