Лаб. работа 4 "Создание индексных страниц и пагинация"

Цель: Научиться выводить данные из БД на страницы приложения, реализовать пагинацию и фильтрацию, использовать связи между моделями.
Задачи:

  • Создать страницу со списком постов.
  • Реализовать пагинацию и сортировку.
  • Отображать связанные данные (категории, теги, комментарии).
  • Стилизовать интерфейс с использованием Tailwind CSS.

Методическая часть

1. Создание контроллера и маршрута

  1. Создайте контроллер PostController, если он не создан ранее:

    1php artisan make:controller PostController --resource
    
  2. Добавьте метод index в app/Http/Controllers/PostController.php:

    1public function index() {
    2    $posts = Post::with(['category', 'tags', 'comments'])
    3                ->orderBy('created_at', 'desc')
    4                ->paginate(10);
    5    return view('posts.index', compact('posts'));
    6}
    
  3. Зарегистрируйте маршрут в routes/web.php:

    1use App\Http\Controllers\PostController;
    2Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
    

2. Создание Blade-шаблона

  1. Шаблон списка постов (resources/views/posts/index.blade.php):
     1@extends('layouts.app')
     2
     3@section('content')
     4    <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
     5        <h1 class="text-3xl font-bold text-gray-900 mb-8">Все посты</h1>
     6
     7        <!-- Фильтры и сортировка -->
     8        <div class="mb-6 flex justify-between items-center">
     9            <div class="flex space-x-4">
    10                <a href="{{ route('posts.index', ['sort' => 'latest']) }}" 
    11                   class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700">
    12                    Новые
    13                </a>
    14                <a href="{{ route('posts.index', ['sort' => 'popular']) }}" 
    15                   class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300">
    16                    Популярные
    17                </a>
    18            </div>
    19        </div>
    20
    21        <!-- Список постов -->
    22        <div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
    23            @foreach ($posts as $post)
    24                <div class="bg-white rounded-lg shadow-md overflow-hidden">
    25                    @if ($post->image)
    26                        <img src="{{ asset('storage/' . $post->image) }}" 
    27                             alt="{{ $post->title }}"
    28                             class="w-full h-48 object-cover">
    29                    @endif
    30                    <div class="p-6">
    31                        <div class="flex items-center mb-2">
    32                            <span class="text-sm text-gray-600">
    33                                {{ $post->created_at->format('d.m.Y') }}
    34                            </span>
    35                            <span class="mx-2">•</span>
    36                            <span class="text-sm text-blue-600">
    37                                {{ $post->category->name }}
    38                            </span>
    39                        </div>
    40                        <h2 class="text-xl font-semibold text-gray-900 mb-2">
    41                            {{ $post->title }}
    42                        </h2>
    43                        <p class="text-gray-600 mb-4">{{ Str::limit($post->content, 150) }}</p>
    44                        <div class="flex items-center justify-between">
    45                            <div class="flex space-x-2">
    46                                @foreach ($post->tags as $tag)
    47                                    <span class="px-2 py-1 bg-gray-100 text-gray-700 rounded-full text-sm">
    48                                        {{ $tag->name }}
    49                                    </span>
    50                                @endforeach
    51                            </div>
    52                            <a href="{{ route('posts.show', $post) }}" 
    53                               class="text-blue-600 hover:text-blue-800">
    54                                Читать →
    55                            </a>
    56                        </div>
    57                    </div>
    58                </div>
    59            @endforeach
    60        </div>
    61
    62        <!-- Пагинация -->
    63        <div class="mt-8">
    64            {{ $posts->links() }}
    65        </div>
    66    </div>
    67@endsection
    

3. Кастомизация пагинации

  1. Создайте файл пагинации для Tailwind:
    1php artisan vendor:publish --tag=laravel-pagination
    
  2. Выберите шаблон resources/views/vendor/pagination/tailwind.blade.php.

4. Добавление сортировки в контроллер

Модифицируйте метод index для поддержки параметров:

 1public function index(Request $request) {
 2    $sort = $request->query('sort', 'latest');
 3
 4    $query = Post::with(['category', 'tags', 'comments']);
 5
 6    switch ($sort) {
 7        case 'popular':
 8            $query->withCount('comments')->orderBy('comments_count', 'desc');
 9            break;
10        default:
11            $query->orderBy('created_at', 'desc');
12    }
13
14    $posts = $query->paginate(10);
15    return view('posts.index', compact('posts', 'sort'));
16}

Индивидуальные задания

1. Фильтрация по категориям

  • Добавьте выпадающий список с категориями в шапку страницы.
  • Реализуйте фильтрацию постов по выбранной категории через параметр URL.

Пример кода:

1<select onchange="window.location.href = '{{ route('posts.index') }}?category=' + this.value">
2    <option value="">Все категории</option>
3    @foreach ($categories as $category)
4        <option value="{{ $category->id }}" {{ $category->id == request('category') ? 'selected' : '' }}>
5            {{ $category->name }}
6        </option>
7    @endforeach
8</select>

2. Поиск по заголовкам

  • Добавьте поле поиска, которое фильтрует посты по вхождению текста в заголовок.
  • Используйте LIKE в SQL-запросе:
    1if ($search = $request->query('search')) {
    2    $query->where('title', 'LIKE', "%{$search}%");
    3}
    

3. Бесконечная прокрутка

  1. Установите пакет для AJAX-пагинации:
    1npm install axios
    
  2. Добавьте в конец шаблона:
     1<div id="posts-container"></div>
     2<div id="loading" class="hidden">Загрузка...</div>
     3
     4<script>
     5    let page = 1;
     6    const loadMore = async () => {
     7        page++;
     8        const response = await axios.get(`/posts?page=${page}`);
     9        document.getElementById('posts-container').innerHTML += response.data;
    10        if (!response.data.includes('paginator')) {
    11            window.removeEventListener('scroll', handleScroll);
    12        }
    13    };
    14
    15    const handleScroll = () => {
    16        if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 500) {
    17            loadMore();
    18        }
    19    };
    20
    21    window.addEventListener('scroll', handleScroll);
    22</script>
    

4. Статистика в сайдбаре

Добавьте блок с общей статистикой:

1<div class="bg-white p-6 rounded-lg shadow-md">
2    <h3 class="font-semibold mb-4">Статистика блога</h3>
3    <ul class="space-y-2">
4        <li>Всего постов: {{ Post::count() }}</li>
5        <li>Всего комментариев: {{ Comment::count() }}</li>
6        <li>Популярный тег: {{ Tag::withCount('posts')->orderBy('posts_count', 'desc')->first()->name }}</li>
7    </ul>
8</div>

Примечания

  • Оптимизация запросов: Используйте with() для жадной загрузки связанных данных.
  • Tailwind-классы: Для адаптивности применяйте grid-сетки (md:grid-cols-2, lg:grid-cols-3).
← Лаб. работа 3 "Создание моделей, миграций и … Лаб. работа 5 "Создание форм для работы с … →