Добавил погинацию (бесконечную) на страницу "Проекты"
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>
|
<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 %}
|
||||||
@ -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:
|
||||||
|
|||||||
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