Добавил погинацию (бесконечную) на страницу "Проекты"
This commit is contained in:
parent
c677d6ee8c
commit
cf5fa86590
50
programmer/static/programmer/js/infinite_scroll.js
Normal file
50
programmer/static/programmer/js/infinite_scroll.js
Normal 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);
|
||||
})();
|
||||
56
programmer/templates/programmer/includes/project_card.html
Normal file
56
programmer/templates/programmer/includes/project_card.html
Normal 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>
|
||||
@ -0,0 +1,5 @@
|
||||
{% autoescape off %}
|
||||
{% for post in posts %}
|
||||
{% include 'programmer/includes/project_card.html' %}
|
||||
{% endfor %}
|
||||
{% endautoescape %}
|
||||
@ -13,75 +13,9 @@
|
||||
<p class="page-subtitle">Реализованные решения и кейсы по автоматизации бизнес-процессов</p>
|
||||
</div>
|
||||
|
||||
<div class="improved-list">
|
||||
{% autoescape off %}
|
||||
{% for p in posts %}
|
||||
<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>
|
||||
<ul class="improved-list" id="projects-container">
|
||||
{% include 'programmer/includes/project_cards.html' %}
|
||||
</ul>
|
||||
|
||||
{% if not posts %}
|
||||
<div class="content-card text-center">
|
||||
@ -90,7 +24,23 @@
|
||||
</div>
|
||||
{% 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 %}
|
||||
@ -1,4 +1,4 @@
|
||||
from django.http import HttpResponse, HttpResponseNotFound, HttpRequest
|
||||
from django.http import HttpResponse, HttpResponseNotFound, HttpRequest, JsonResponse
|
||||
from .models import *
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib import messages
|
||||
@ -14,6 +14,7 @@ from django.views.generic import TemplateView, ListView
|
||||
from .mixins import PageViewTrackingMixin, MenuContextMixin
|
||||
from .services import get_published_queryset, track_page_view
|
||||
from typing import Any, Dict, Type
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
|
||||
# menu = [
|
||||
@ -46,6 +47,27 @@ class BaseListView(PageViewTrackingMixin, MenuContextMixin, ListView):
|
||||
context.update(self.extra_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):
|
||||
"""Главная страница с формой обратной связи."""
|
||||
@ -101,6 +123,7 @@ class SolutionListView(BaseListView):
|
||||
template_name = 'programmer/solution.html'
|
||||
context_object_name = 'posts'
|
||||
paginate_by = 1 # Количество проектов на странице
|
||||
cards_template = 'programmer/includes/project_cards.html'
|
||||
ordering = ['-time_create']
|
||||
|
||||
def get_queryset(self) -> QuerySet:
|
||||
|
||||
50
static/programmer/js/infinite_scroll.js
Normal file
50
static/programmer/js/infinite_scroll.js
Normal 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);
|
||||
})();
|
||||
Loading…
x
Reference in New Issue
Block a user