15-й урок создания интернет-магазина на Laravel: страница категории

15-й урок создания интернет-магазина на Laravel: страница категории

В процессе разработки интернет-магазина на Laravel одним из ключевых этапов является создание страницы категории товаров. Эта страница играет важную роль в навигации пользователей по сайту и помогает им быстро находить нужные товары. В данной статье будет подробно рассмотрен процесс создания и оптимизации страницы категории для интернет-магазина на Laravel.

Содержание

  • Подготовка проекта и настройка базы данных
  • Создание модели Category и миграции
  • Разработка контроллера CategoryController
  • Реализация маршрутизации для страницы категории
  • Создание представления для страницы категории
  • Стилизация страницы категории с использованием CSS
  • Добавление функциональности фильтрации товаров
  • Реализация пагинации для списка товаров
  • Оптимизация запросов к базе данных
  • Кеширование данных категории
  • Добавление хлебных крошек
  • SEO-оптимизация страницы категории
  • Тестирование функциональности страницы категории
  • Рекомендации по дальнейшему улучшению

Подготовка проекта и настройка базы данных

Перед началом работы над страницей категории необходимо убедиться, что проект Laravel настроен корректно и база данных готова к использованию. Для этого следует выполнить следующие шаги:

  1. Создать новый проект Laravel или открыть существующий.
  2. Настроить подключение к базе данных в файле .env.
  3. Убедиться, что все необходимые пакеты установлены и обновлены.

После выполнения этих шагов можно приступать к созданию модели Category и соответствующей миграции.

Создание модели Category и миграции

Для работы с категориями товаров необходимо создать модель Category и соответствующую миграцию. Это можно сделать с помощью следующих команд в терминале:

php artisan make:model Category -m

Эта команда создаст файл модели Category.php в директории app/Models и файл миграции в директории database/migrations. Теперь нужно определить структуру таблицы категорий в файле миграции:

 public function up() { Schema::create('categories', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('slug')->unique(); $table->text('description')->nullable(); $table->string('image')->nullable(); $table->unsignedBigInteger('parent_id')->nullable(); $table->foreign('parent_id')->references('id')->on('categories')->onDelete('cascade'); $table->timestamps(); }); } 

После создания миграции нужно выполнить ее с помощью команды:

php artisan migrate

Разработка контроллера CategoryController

Следующим шагом является создание контроллера CategoryController, который будет отвечать за обработку запросов, связанных со страницей категории. Для создания контроллера используется команда:

php artisan make:controller CategoryController

В созданном файле CategoryController.php необходимо реализовать метод show, который будет отвечать за отображение страницы категории:

 public function show($slug) { $category = Category::where('slug', $slug)->firstOrFail(); $products = $category->products()->paginate(12); return view('category.show', compact('category', 'products')); } 

Реализация маршрутизации для страницы категории

Для того чтобы страница категории была доступна по определенному URL, необходимо добавить соответствующий маршрут в файл routes/web.php:

Route::get('category/{slug}', [CategoryController::class, 'show'])->name('category.show');

Этот маршрут будет обрабатывать запросы вида /category/electronics и передавать управление методу show контроллера CategoryController.

Создание представления для страницы категории

Теперь необходимо создать представление (view) для страницы категории. Создайте файл resources/views/category/show.blade.php и добавьте в него базовую структуру HTML:

 @extends('layouts.app') @section('content') 

{{ $category->name }}

{{ $category->description }}

@foreach($products as $product)
{{ $product- data-src=name }}">

{{ $product->name }}

{{ $product->price }}

slug) }}">Подробнее
@endforeach
{{ $products->links() }} @endsection

Стилизация страницы категории с использованием CSS

Для улучшения внешнего вида страницы категории необходимо добавить стили CSS. Создайте файл public/css/category.css и добавьте в него следующие стили:

 .products-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; } .product-card { border: 1px solid #e0e0e0; border-radius: 5px; padding: 15px; text-align: center; } .product-card img { max-width: 100%; height: auto; } .product-card h3 { margin: 10px 0; } .product-card p { font-weight: bold; color: #4CAF50; } .product-card a { display: inline-block; margin-top: 10px; padding: 5px 10px; background-color: #007bff; color: #fff; text-decoration: none; border-radius: 3px; } 

Не забудьте подключить этот файл стилей в вашем основном шаблоне layouts/app.blade.php:

<link rel="stylesheet" href="{{ asset('css/category.css') }}">

Добавление функциональности фильтрации товаров

Для улучшения пользовательского опыта можно добавить функцию фильтрации товаров на странице категории. Для этого необходимо обновить метод show в CategoryController:

 public function show($slug, Request $request) { $category = Category::where('slug', $slug)->firstOrFail(); $query = $category->products(); if ($request->has('min_price')) { $query->where('price', '>=', $request->min_price); } if ($request->has('max_price')) { $query->where('price', '<=', $request->max_price); } if ($request->has('sort')) { $query->orderBy($request->sort, $request->order ?? 'asc'); } $products = $query->paginate(12); return view('category.show', compact('category', 'products')); } 

Теперь нужно добавить форму фильтрации в представление category/show.blade.php:

 

Реализация пагинации для списка товаров

Laravel предоставляет встроенную поддержку пагинации, которую мы уже использовали в методе show контроллера CategoryController. Для отображения ссылок пагинации в представлении используется метод links():

{{ $products->links() }}

Если требуется настроить внешний вид пагинации, можно опубликовать шаблоны пагинации с помощью команды:

php artisan vendor:publish --tag=laravel-pagination

После этого можно изменять шаблоны пагинации в директории resources/views/vendor/pagination.

Оптимизация запросов к базе данных

Для улучшения производительности страницы категории следует оптимизировать запросы к базе данных. Одним из способов является использование метода eager loading для загрузки связанных данных:

 public function show($slug, Request $request) { $category = Category::where('slug', $slug)->firstOrFail(); $query = $category->products()->with('brand', 'reviews'); // ... остальной код метода $products = $query->paginate(12); return view('category.show', compact('category', 'products')); } 

Это позволит избежать проблемы N+1 запросов при загрузке связанных данных для каждого товара.

Кеширование данных категории

Для дальнейшего повышения производительности можно использовать кеширование данных категории. Добавьте следующий код в метод show контроллера CategoryController:

 use Illuminate\Support\Facades\Cache; public function show($slug, Request $request) { $category = Cache::remember('category_'.$slug, 3600, function () use ($slug) { return Category::where('slug', $slug)->firstOrFail(); }); // ... остальной код метода } 

Этот код будет кешировать данные категории на 1 час, что уменьшит нагрузку на базу данных при частых запросах к странице категории.

Добавление хлебных крошек

Для улучшения навигации по сайту и SEO можно добавить хлебные крошки на страницу категории. Создайте частичное представление resources/views/partials/breadcrumbs.blade.php:

  

Затем включите это частичное представление в category/show.blade.php:

@include('partials.breadcrumbs')

SEO-оптимизация страницы категории

Для улучшения SEO-показателей страницы категории необходимо выполнить следующие шаги:

  1. Добавить мета-теги title и description:
     @section('meta_title', $category->name . ' - Интернет-магазин') @section('meta_description', 'Купить ' . $category->name . ' в нашем интернет-магазине. Большой выбор, низкие цены, быстрая доставка.') 
  2. Использовать семантическую разметку HTML5:
     

    {{ $category->name }}

    {{ $category->description }}

    section class="products">
  3. Добавить микроразметку Schema.org для категории:
      
  4. Оптимизировать URL-адреса товаров, используя ЧПУ (человекопонятные URL):
    Route::get('category/{category_slug}/{product_slug}', [ProductController::class, 'show'])->name('product.show');

Тестирование функциональности страницы категории

После реализации всех вышеперечисленных функций необходимо провести тщательное тестирование страницы категории. Для этого можно использовать как ручное тестирование, так и автоматизированные тесты.

Пример функционального теста для страницы категории:

 use Tests\TestCase; use App\Models\Category; use App\Models\Product; class CategoryPageTest extends TestCase { public function test_category_page_displays_correct_products() { $category = Category::factory()->create(); $products = Product::factory()->count(5)->create(['category_id' => $category->id]); $response = $this->get(route('category.show', $category->slug)); $response->assertStatus(200); $response->assertSee($category->name); foreach ($products as $product) { $response->assertSee($product->name); } } public function test_category_page_filters_products_by_price() { $category = Category::factory()->create(); $cheapProduct = Product::factory()->create(['category_id' => $category->id, 'price' => 10]); $expensiveProduct = Product::factory()->create(['category_id' => $category->id, 'price' => 100]); $response = $this->get(route('category.show', [ 'slug' => $category->slug, 'min_price' => 50, ])); $response->assertStatus(200); $response->assertDontSee($cheapProduct->name); $response->assertSee($expensiveProduct->name); } } 

Рекомендации по дальнейшему улучшению

После успешной реализации основной функциональности страницы категории можно рассмотреть следующие улучшения:

  • Добавление функции «живого» поиска товаров с использованием AJAX.
  • Реализация сравнения товаров внутри категории.
  • Внедрение системы рекомендаций похожих товаров.
  • Оптимизация загрузки изображений с использованием lazy loading.
  • Добавление возможности просмотра товаров в виде списка или сетки.
  • Реализация фильтрации по атрибутам товаров, специфичным для каждой категории.

Оптимизация производительности страницы категории

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

Кеширование запросов к базе данных

Помимо кеширования данных категории, можно также кешировать результаты запросов для товаров. Это особенно полезно, если фильтры и сортировка не меняются часто:

 use Illuminate\Support\Facades\Cache; public function show($slug, Request $request) { $category = Cache::remember('category_'.$slug, 3600, function () use ($slug) { return Category::where('slug', $slug)->firstOrFail(); }); $cacheKey = 'products_' . $slug . '_' . md5(json_encode($request->all())); $products = Cache::remember($cacheKey, 1800, function () use ($category, $request) { $query = $category->products()->with('brand', 'reviews'); // Apply filters and sorting // ... return $query->paginate(12); }); return view('category.show', compact('category', 'products')); } 

Оптимизация загрузки изображений

Для ускорения загрузки страницы категории важно оптимизировать работу с изображениями. Можно использовать следующие методы:

  1. Применение lazy loading для изображений товаров:
    <img src="{{ $product->image }}" alt="{{ $product->name }}" loading="lazy">
  2. Использование адаптивных изображений с помощью тега <picture>:
        {{ $product- data-src=name }}" loading="lazy">  
  3. Оптимизация изображений при загрузке с помощью пакета intervention/image:
     use Intervention\Image\Facades\Image;
    public function uploadProductImage($image)
    {
    $filename = time() . '.' . $image->getClientOriginalExtension();
    $path = public_path('images/products/' . $filename);
    
    
    
    Image::make($image)->resize(800, null, function ($constraint) {
        $constraint->aspectRatio();
        $constraint->upsize();
    })->save($path, 80);
    
    return 'images/products/' . $filename;
    }
    

Минификация и объединение CSS и JavaScript файлов

Для уменьшения количества HTTP-запросов и объема передаваемых данных следует минифицировать и объединить CSS и JavaScript файлы. Это можно сделать с помощью Laravel Mix:

 // webpack.mix.js const mix = require('laravel-mix'); mix.js('resources/js/app.js', 'public/js') .sass('resources/sass/app.scss', 'public/css') .styles([ 'public/css/normalize.css', 'public/css/main.css', 'public/css/category.css' ], 'public/css/all.css') .version(); 

Затем в шаблоне используйте сгенерированные файлы:

  

Использование CDN для статических ресурсов

Для ускорения загрузки статических ресурсов (изображения, CSS, JavaScript) рекомендуется использовать CDN (Content Delivery Network). Это позволит распределить нагрузку и уменьшить время отклика для пользователей из разных регионов.

Пример настройки CDN в файле .env:

 ASSET_URL=https://your-cdn-domain.com 

После этого все вызовы функции asset() будут использовать указанный домен CDN.

Реализация бесконечной прокрутки

Вместо классической пагинации можно реализовать бесконечную прокрутку, которая позволит загружать новые товары по мере прокрутки страницы. Для этого потребуется использовать AJAX-запросы и JavaScript.

Пример реализации на JavaScript с использованием библиотеки Axios:

 // resources/js/infinite-scroll.js import axios from 'axios'; let page = 1; const container = document.querySelector('.products-grid'); const loadingIndicator = document.createElement('div'); loadingIndicator.textContent = 'Загрузка...'; function loadMoreProducts() { if (loadingIndicator.parentNode) return; container.appendChild(loadingIndicator); axios.get(`${window.location.href}?page=${++page}`) .then(response => { const parser = new DOMParser(); const html = parser.parseFromString(response.data, 'text/html'); const newProducts = html.querySelectorAll('.product-card'); newProducts.forEach(product => { container.appendChild(product); }); container.removeChild(loadingIndicator); }) .catch(error => { console.error('Ошибка при загрузке товаров:', error); container.removeChild(loadingIndicator); }); } window.addEventListener('scroll', () => { if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 500) { loadMoreProducts(); } }); 

Не забудьте подключить этот скрипт в ваш основной JavaScript файл:

 // resources/js/app.js import './infinite-scroll'; 

Оптимизация SQL-запросов

Для улучшения производительности страницы категории важно оптимизировать SQL-запросы. Рассмотрим несколько способов оптимизации:

  1. Использование индексов для часто запрашиваемых полей:
     // В миграции для таблицы products Schema::table('products', function (Blueprint $table) { $table->index('category_id'); $table->index('price'); }); 
  2. Ограничение выборки только необходимыми полями:
     $products = $category->products() ->select('id', 'name', 'price', 'image', 'slug') ->with(['brand:id,name', 'reviews:id,product_id,rating']) ->paginate(12); 
  3. Использование подзапросов для оптимизации сложных выборок:
     $products = Product::whereIn('category_id', function ($query) use ($category) { $query->select('id') ->from('categories') ->where('id', $category->id) ->orWhere('parent_id', $category->id); })->paginate(12); 

Реализация AJAX-фильтрации

Для улучшения пользовательского опыта можно реализовать AJAX-фильтрацию товаров без перезагрузки страницы. Для этого необходимо создать отдельный метод в контроллере и обновить JavaScript-код на клиентской стороне.

Добавьте метод в CategoryController:

 public function filter(Request $request, $slug) { $category = Category::where('slug', $slug)->firstOrFail(); $query = $category->products()->with('brand', 'reviews'); // Apply filters if ($request->has('min_price')) { $query->where('price', '>=', $request->min_price); } if ($request->has('max_price')) { $query->where('price', '<=', $request->max_price); } if ($request->has('sort')) { $query->orderBy($request->sort, $request->order ?? 'asc'); } $products = $query->paginate(12); return response()->json([ 'html' => view('partials.product_grid', compact('products'))->render(), 'pagination' => $products->links()->toHtml(), ]); } 

Создайте частичное представление для сетки товаров (resources/views/partials/product_grid.blade.php):

 @foreach($products as $product) 
{{ $product- data-src=name }}" loading="lazy">

{{ $product->name }}

{{ $product->price }}

slug) }}">Подробнее
@endforeach

Обновите JavaScript-код для обработки AJAX-запросов:

 // resources/js/filter.js import axios from 'axios'; const filterForm = document.querySelector('#filter-form'); const productsContainer = document.querySelector('.products-grid'); const paginationContainer = document.querySelector('.pagination'); filterForm.addEventListener('submit', function(e) { e.preventDefault(); const formData = new FormData(this); const url = `${this.action}?${new URLSearchParams(formData).toString()}`; axios.get(url) .then(response => { productsContainer.innerHTML = response.data.html; paginationContainer.innerHTML = response.data.pagination; history.pushState(null, '', url); }) .catch(error => { console.error('Ошибка при фильтрации товаров:', error); }); }); 

Добавление функции сравнения товаров

Функция сравнения товаров может быть полезной для пользователей при выборе товаров в одной категории. Для реализации этой функции необходимо выполнить следующие шаги:

  1. Создать таблицу для хранения товаров для сравнения:
     // database/migrations/create_compare_items_table.php Schema::create('compare_items', function (
    Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('user_id')->nullable();
    $table->string('session_id');
    $table->unsignedBigInteger('product_id');
    $table->timestamps();
    
    
    
    $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
    $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
    });
    
  2. Создать модель CompareItem:
     // app/Models/CompareItem.php namespace App\Models;
    use Illuminate\Database\Eloquent\Model;
    
    class CompareItem extends Model
    {
    protected $fillable = ['user_id', 'session_id', 'product_id'];
    
    
    
    public function product()
    {
        return $this->belongsTo(Product::class);
    }
    }
    
  3. Добавить методы в ProductController для добавления и удаления товаров из сравнения:
     // app/Http/Controllers/ProductController.php public function addToCompare(Request $request, $productId) { $sessionId = $request->session()->getId(); $userId = auth()->id();
    
    
    CompareItem::updateOrCreate(
        ['session_id' => $sessionId, 'product_id' => $productId],
        ['user_id' => $userId]
    );
    
    return response()->json(['message' => 'Товар добавлен к сравнению']);
    }
    
    public function removeFromCompare(Request $request, $productId)
    {
    $sessionId = $request->session()->getId();
    
    
    
    CompareItem::where('session_id', $sessionId)
        ->where('product_id', $productId)
        ->delete();
    
    return response()->json(['message' => 'Товар удален из сравнения']);
    }
    
    public function compare(Request $request)
    {
    $sessionId = $request->session()->getId();
    $compareItems = CompareItem::where('session_id', $sessionId)
    ->with('product')
    ->get();
    
    
    
    return view('compare', compact('compareItems'));
    }
    
  4. Добавить кнопки «Добавить к сравнению» на странице категории:
     // resources/views/category/show.blade.php @foreach($products as $product) 
    @endforeach
  5. Реализовать JavaScript для обработки добавления товаров к сравнению:
     // resources/js/compare.js document.querySelectorAll('.add-to-compare').forEach(button => { button.addEventListener('click', function() { const productId = this.dataset.productId; axios.post(`/compare/add/${productId}`) .then(response => { alert(response.data.message); }) .catch(error => { console.error('Ошибка при добавлении товара к сравнению:', error); }); }); }); 

Внедрение системы рекомендаций похожих товаров

Система рекомендаций похожих товаров может повысить конверсию и улучшить пользовательский опыт. Рассмотрим простой способ реализации такой системы на основе категорий и тегов товаров:

  1. Добавьте метод в модель Product для получения похожих товаров:
     // app/Models/Product.php public function getSimilarProducts($limit = 4) { return $this->where('category_id', $this->category_id) ->where('id', '!=', $this->id) ->inRandomOrder() ->limit($limit) ->get(); } 
  2. Добавьте вывод похожих товаров на странице товара:
     // resources/views/product/show.blade.php 

    Похожие товары

    @foreach($product->getSimilarProducts() as $similarProduct)
    {{ $similarProduct- data-src=name }}">

    {{ $similarProduct->name }}

    {{ $similarProduct->price }}

    slug) }}">Подробнее
    @endforeach

Оптимизация для мобильных устройств

Для улучшения пользовательского опыта на мобильных устройствах необходимо оптимизировать страницу категории. Вот несколько рекомендаций:

  1. Использовать адаптивный дизайн с помощью медиа-запросов:
     // resources/sass/category.scss .products-grid { display: grid; grid-template-columns: repeat(1, 1fr); gap: 10px;
    
    
    @media (min-width: 576px) {
        grid-template-columns: repeat(2, 1fr);
    }
    
    @media (min-width: 768px) {
        grid-template-columns: repeat(3, 1fr);
        gap: 20px;
    }
    
    @media (min-width: 992px) {
        grid-template-columns: repeat(4, 1fr);
    }
    }
    
  2. Оптимизировать загрузку изображений для мобильных устройств:
        {{ $product- data-src=name }}" loading="lazy">  
  3. Использовать touch-friendly элементы управления:
     // resources/views/category/show.blade.php 
    Фильтры

Реализация функции «быстрый просмотр»

Функция «быстрый просмотр» позволяет пользователям просматривать основную информацию о товаре без перехода на отдельную страницу. Это может улучшить пользовательский опыт и увеличить конверсию. Вот пример реализации:

  1. Добавьте кнопку «Быстрый просмотр» к каждому товару на странице категории:
     // resources/views/category/show.blade.php @foreach($products as $product) 
    @endforeach
  2. Создайте маршрут и метод контроллера для получения данных товара:
     // routes/web.php Route::get('/product/{id}/quick-view', [ProductController::class, 'quickView'])->name('product.quick-view');
    // app/Http/Controllers/ProductController.php
    public function quickView($id)
    {
    $product = Product::with('brand')->findOrFail($id);
    return response()->json([
    'html' => view('partials.quick_view', compact('product'))->render()
    ]);
    }
    
  3. Создайте частичное представление для быстрого просмотра:
     // resources/views/partials/quick_view.blade.php 

    {{ $product->name }}

    {{ $product- data-src=name }}">

    Цена: {{ $product->price }}

    Бренд: {{ $product->brand->name }}

    {{ $product->short_description }}

    slug) }}">Подробнее
  4. Реализуйте JavaScript для обработки быстрого просмотра:
     // resources/js/quick-view.js import axios from 'axios';
    document.querySelectorAll('.quick-view').forEach(button => {
    button.addEventListener('click', function() {
    const productId = this.dataset.productId;
    axios.get(/product/${productId}/quick-view)
    .then(response => {
    const modal = document.createElement('div');
    modal.classList.add('modal');
    modal.innerHTML = response.data.html;
    document.body.appendChild(modal);
    
    
    
                modal.querySelector('.close').addEventListener('click', () => {
                    document.body.removeChild(modal);
                });
            })
            .catch(error => {
                console.error('Ошибка при загрузке быстрого просмотра:', error);
            });
    });
    });
    

Реализация фильтрации по атрибутам товаров

Фильтрация по атрибутам товаров позволяет пользователям быстро находить нужные товары в категории. Для реализации этой функции необходимо выполнить следующие шаги:

  1. Создать таблицы для атрибутов и значений атрибутов:
     // database/migrations/create_attributes_table.php Schema::create('attributes', function (Blueprint $table) { $table->id(); $table->string('name'); $table->timestamps(); });
    // database/migrations/create_attribute_values_table.php
    Schema::create('attribute_values', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('attribute_id');
    $table->string('value');
    $table->timestamps();
    
    
    
    $table->foreign('attribute_id')->references('id')->on('attributes')->onDelete('cascade');
    });
    
    // database/migrations/create_product_attribute_value_table.php
    Schema::create('product_attribute_value', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('product_id');
    $table->unsignedBigInteger('attribute_value_id');
    
    
    
    $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
    $table->foreign('attribute_value_id')->references('id')->on('attribute_values')->onDelete('cascade');
    });
    
  2. Создать модели для атрибутов и значений атрибутов:
     // app/Models/Attribute.php class Attribute extends Model { protected $fillable = ['name'];
    
    
    public function values()
    {
        return $this->hasMany(AttributeValue::class);
    }
    }
    
    // app/Models/AttributeValue.php
    class AttributeValue extends Model
    {
    protected $fillable = ['attribute_id', 'value'];
    
    
    
    public function attribute()
    {
        return $this->belongsTo(Attribute::class);
    }
    
    public function products()
    {
        return $this->belongsToMany(Product::class);
    }
    }
    
  3. Обновить модель Product:
     // app/Models/Product.php class Product extends Model { // ...
    
    
    public function attributeValues()
    {
        return $this->belongsToMany(AttributeValue::class);
    }
    }
    
  4. Обновить метод show в CategoryController для учета фильтрации по атрибутам:
     // app/Http/Controllers/CategoryController.php public function show($slug, Request $request) { $category = Category::where('slug', $slug)->firstOrFail(); $query = $category->products()->with('attributeValues.attribute');
    
    
    // Apply price filters
    // ...
    
    // Apply attribute filters
    if ($request->has('attributes')) {
        foreach ($request->attributes as $attributeId => $valueIds) {
            $query->whereHas('attributeValues', function ($q) use ($attributeId, $valueIds) {
                $q->whereIn('attribute_values.id', $valueIds)
                  ->where('attributes.id', $attributeId);
            });
        }
    }
    
    $products = $query->paginate(12);
    
    $attributes = Attribute::with(['values' => function ($query) use ($category) {
        $query->whereHas('products', function ($q) use ($category) {
            $q->where('category_id', $category->id);
        });
    }])->get();
    
    return view('category.show', compact('category', 'products', 'attributes'));
    }
    
  5. Обновить представление для отображения фильтров по атрибутам:
     // resources/views/category/show.blade.php 
    @foreach($attributes as $attribute)

    {{ $attribute->name }}

    @foreach($attribute->values as $value) @endforeach @endforeach

Улучшение SEO-оптимизации страницы категории

Для улучшения позиций сайта в поисковых системах необходимо уделить особое внимание SEO-оптимизации страницы категории. Рассмотрим несколько дополнительных методов оптимизации:

  1. Использование канонических URL:
     // resources/views/category/show.blade.php @section('canonical')  @endsection 
  2. Добавление метатегов Open Graph для улучшения отображения страницы при шеринге в социальных сетях:
     @section('meta')      @endsection 
  3. Реализация автоматической генерации meta-описаний для категорий:
     // app/Models/Category.php public function getMetaDescription() { $productCount = $this->products()->count(); $brandNames = $this->products()->distinct()->pluck('brand')->take(3)->implode(', ');
    
    
    return "Купите {$this->name} в нашем интернет-магазине. {$productCount} товаров от ведущих брендов: {$brandNames}. Доставка по всей России.";
    }
    
  4. Добавление структурированных данных для категории:
     // resources/views/category/show.blade.php @section('structured_data')  @endsection 

Оптимизация скорости загрузки страницы

Скорость загрузки страницы является важным фактором как для пользовательского опыта, так и для SEO. Рассмотрим несколько способов оптимизации скорости загрузки страницы категории:

  1. Использование кеширования на уровне приложения:
     // app/Http/Controllers/CategoryController.php use Illuminate\Support\Facades\Cache;
    public function show($slug, Request $request)
    {
    $cacheKey = "category_{$slug}_" . md5(json_encode($request->all()));
    
    
    
    return Cache::remember($cacheKey, now()->addMinutes(30), function () use ($slug, $request) {
        // Логика получения данных для страницы категории
        // ...
        
        return view('category.show', compact('category', 'products', 'attributes'));
    });
    }
    
  2. Отложенная загрузка изображений с использованием Intersection Observer API:
     // resources/js/lazy-loading.js document.addEventListener('DOMContentLoaded', function() { const images = document.querySelectorAll('img[data-src]'); const config = { rootMargin: '50px 0px', threshold: 0.01 };
    
    
    let observer = new IntersectionObserver(function(entries, self) {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                preloadImage(entry.target);
                self.unobserve(entry.target);
            }
        });
    }, config);
    
    images.forEach(image => {
        observer.observe(image);
    });
    });
    
    function preloadImage(img) {
    const src = img.getAttribute('data-src');
    if (!src) { return; }
    img.src = src;
    }
    
  3. Оптимизация загрузки CSS и JavaScript:
     // webpack.mix.js const mix = require('laravel-mix');
    mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .options({
    terser: {
    extractComments: false,
    }
    })
    .version();
    
  4. Использование HTTP/2 для параллельной загрузки ресурсов:
     // config/webserver/nginx.conf server { listen 443 ssl http2; # ... } 

Улучшение пользовательского опыта

Для повышения конверсии и удержания пользователей на странице категории можно реализовать следующие улучшения пользовательского опыта:

  1. Добавление функции «живого» поиска по товарам в категории:
     // resources/views/category/show.blade.php 
    // resources/js/live-search.js
    import axios from 'axios';
    import debounce from 'lodash/debounce';
    
    const liveSearch = document.getElementById('live-search');
    const productsContainer = document.querySelector('.products-grid');
    
    const performSearch = debounce((query) => {
    axios.get(/api/category/${categorySlug}/search, { params: { query } })
    .then(response => {
    productsContainer.innerHTML = response.data.html;
    })
    .catch(error => {
    console.error('Ошибка при выполнении поиска:', error);
    });
    }, 300);
    
    liveSearch.addEventListener('input', (e) => {
    performSearch(e.target.value);
    });
    
  2. Реализация функции сравнения товаров:
     // resources/views/category/show.blade.php 
    // resources/js/compare.js
    const compareButtons = document.querySelectorAll('.compare-btn');
    const compareList = [];
    
    compareButtons.forEach(button => {
    button.addEventListener('click', function() {
    const productId = this.dataset.productId;
    if (compareList.includes(productId)) {
    compareList.splice(compareList.indexOf(productId), 1);
    this.textContent = 'Добавить к сравнению';
    } else {
    compareList.push(productId);
    this.textContent = 'Удалить из сравнения';
    }
    
    
    
        updateComparePanel();
    });
    });
    
    function updateComparePanel() {
    // Обновление панели сравнения
    }
    
  3. Добавление функции «Просмотренные товары»:
     // app/Http/Controllers/CategoryController.php public function show($slug, Request $request) { // ... $recentlyViewed = $request->session()->get('recently_viewed', []); $recentlyViewedProducts = Product::whereIn('id', $recentlyViewed)->get();
    
    
    return view('category.show', compact('category', 'products', 'attributes', 'recentlyViewedProducts'));
    }
    
    // resources/views/category/show.blade.php
    @if($recentlyViewedProducts->isNotEmpty())
    

    Недавно просмотренные товары

    @foreach($recentlyViewedProducts as $product) @endforeach
    @endif
  4. Реализация функции «Похожие товары»:
     // app/Models/Product.php public function getSimilarProducts($limit = 4) { return $this->where('category_id', $this->category_id) ->where('id', '!=', $this->id) ->inRandomOrder() ->limit($limit) ->get(); }
    // resources/views/category/show.blade.php
    @foreach($products as $product)
    

    Похожие товары

    @foreach($product->getSimilarProducts() as $similarProduct) @endforeach
    @endforeach

Оптимизация для мобильных устройств

Учитывая растущую долю мобильного трафика, важно оптимизировать страницу категории для мобильных устройств:

  1. Использование адаптивного дизайна:
     // resources/sass/category.scss .products-grid { display: grid; grid-template-columns: repeat(1, 1fr); gap: 10px;
    
    
    @media (min-width: 576px) {
        grid-template-columns: repeat(2, 1fr);
    }
    
    @media (min-width: 768px) {
        grid-template-columns: repeat(3, 1fr);
    }
    
    @media (min-width: 992px) {
        grid-template-columns: repeat(4, 1fr);
    }
    }
    
  2. Оптимизация отображения фильтров на мобильных устройствах:
     // resources/views/category/show.blade.php  
    // resources/js/mobile-filters.js const filterToggle = document.querySelector('.mobile-filter-toggle'); const filterPanel = document.querySelector('.mobile-filter-panel'); filterToggle.addEventListener('click', () => { filterPanel.classList.toggle('active'); });
  3. Оптимизация загрузки изображений для мобильных устройств:
     // resources/views/category/show.blade.php    {{ $product- data-src=name }}" loading="lazy">  

Интеграция с аналитикой

Для отслеживания эффективности страницы категории и поведения пользователей важно интегрировать систему аналитики:

  1. Добавление Google Analytics:
     // resources/views/layouts/app.blade.php   
    
  2. Отслеживание событий на странице категории:
     // resources/js/analytics.js function trackEvent(category, action, label) { gtag('event', action, { 'event_category': category, 'event_label': label }); }
    // Отслеживание применения фильтров
    document.querySelector('.filter-form').addEventListener('submit', () => {
    trackEvent('Category', 'Apply Filters', categoryName);
    });
    
    // Отслеживание добавления товара в корзину
    document.querySelectorAll('.add-to-cart').forEach(button => {
    button.addEventListener('click', () => {
    const productName = button.dataset.productName;
    trackEvent('Category', 'Add to Cart', productName);
    });
    });
    

Тестирование и оптимизация

После реализации всех улучшений важно провести тщательное тестирование и оптимизацию страницы категории:

  1. Проведение A/B-тестирования различных вариантов отображения товаров:
     // app/Http/Controllers/CategoryController.php public function show($slug, Request $request) { // ... $abTestVariant = $request->session()->get('ab_test_variant', rand(0, 1)); $request->session()->put('ab_test_variant', $abTestVariant);
    
    
    return view('category.show', compact('category', 'products', 'attributes', 'abTestVariant'));
    }
    
    // resources/views/category/show.blade.php
    @if($abTestVariant == 0)
    
    @else
    
    @endif
    
  2. Оптимизация производительности:
     // config/database.php 'mysql' => [ // ... 'strict' => false, // Отключение строгого режима для оптимизации некоторых запросов ],
    // app/Providers/AppServiceProvider.php
    use Illuminate\Support\Facades\DB;
    
    public function boot()
    {
    DB::connection()->disableQueryLog(); // Отключение логирования запросов в production
    }
    
  3. Проведение нагрузочного тестирования:
     // Пример использования Apache Bench для нагрузочного тестирования ab -n 1000 -c 50 http://your-site.com/category/electronics 
  4. Мониторинг производительности в реальном времени:
     // config/logging.php 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['single', 'newrelic'], ], 'newrelic' => [ 'driver' => 'newrelic', ], ], 

Дополнительные функции и улучшения

Для дальнейшего улучшения страницы категории можно рассмотреть следующие дополнительные функции:

  1. Реализация функции «Избранное»:
     // resources/views/category/show.blade.php 
    // resources/js/favorite.js
    import axios from 'axios';
    
    document.querySelectorAll('.favorite-btn').forEach(button => {
    button.addEventListener('click', function() {
    const productId = this.dataset.productId;
    axios.post('/api/favorite', { product_id: productId })
    .then(response => {
    this.classList.toggle('active');
    })
    .catch(error => {
    console.error('Ошибка при добавлении в избранное:', error);
    });
    });
    });
    
  2. Добавление функции «Поделиться»:
     // resources/views/category/show.blade.php  
  3. Реализация функции «Просмотр в режиме каталога/списка»:
     // resources/views/category/show.blade.php 
    // resources/js/view-mode.js const gridViewBtn = document.querySelector('.grid-view'); const listViewBtn = document.querySelector('.list-view'); const productsContainer = document.querySelector('.products-container'); gridViewBtn.addEventListener('click', () => { productsContainer.classList.remove('list-mode'); productsContainer.classList.add('grid-mode'); gridViewBtn.classList.add('active'); listViewBtn.classList.remove('active'); }); listViewBtn.addEventListener('click', () => { productsContainer.classList.remove('grid-mode'); productsContainer.classList.add('list-mode'); listViewBtn.classList.add('active'); gridViewBtn.classList.remove('active'); });
  4. Добавление функции «Быстрый просмотр»:
     // resources/views/category/show.blade.php 
    // resources/js/quick-view.js
    import axios from 'axios';
    
    document.querySelectorAll('.quick-view-btn').forEach(button => {
    button.addEventListener('click', function() {
    const productId = this.dataset.productId;
    axios.get(/api/product/${productId}/quick-view)
    .then(response => {
    const modal = document.createElement('div');
    modal.classList.add('quick-view-modal');
    modal.innerHTML = response.data.html;
    document.body.appendChild(modal);
    
    
    
                modal.querySelector('.close-modal').addEventListener('click', () => {
                    document.body.removeChild(modal);
                });
            })
            .catch(error => {
                console.error('Ошибка при загрузке быстрого просмотра:', error);
            });
    });
    });
    

Оптимизация для поисковых систем

Для улучшения позиций страницы категории в поисковых системах можно предпринять следующие шаги:

  1. Оптимизация мета-тегов:
     // resources/views/category/show.blade.php @section('meta') {{ $category->name }} - Купить в интернет-магазине   @endsection 
  2. Добавление микроразметки Schema.org:
     // resources/views/category/show.blade.php  
  3. Оптимизация URL-структуры:
     // routes/web.php Route::get('/{category}/{subcategory?}', [CategoryController::class, 'show']) ->name('category.show') ->where('category', '[a-z0-9-]+') ->where('subcategory', '[a-z0-9-]+'); 
  4. Добавление хлебных крошек:
     // resources/views/category/show.blade.php  

Оптимизация загрузки страницы

Для улучшения скорости загрузки страницы категории можно применить следующие техники:

  1. Использование кеширования на стороне сервера:
     // app/Http/Controllers/CategoryController.php use Illuminate\Support\Facades\Cache;
    public function show($slug)
    {
    $category = Cache::remember("category:{$slug}", 3600, function () use ($slug) {
    return Category::where('slug', $slug)->firstOrFail();
    });
    
    
    
    $products = Cache::remember("category:{$slug}:products", 1800, function () use ($category) {
        return $category->products()->paginate(16);
    });
    
    return view('category.show', compact('category', 'products'));
    }
    
  2. Оптимизация загрузки изображений:
     // resources/views/category/show.blade.php {{ $product->name }}
    // resources/js/lazy-load.js
    document.addEventListener("DOMContentLoaded", function() {
    let lazyImages = [].slice.call(document.querySelectorAll("img.lazy-load"));
    
    
    
    if ("IntersectionObserver" in window) {
        let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
            entries.forEach(function(entry) {
                if (entry.isIntersecting) {
                    let lazyImage = entry.target;
                    lazyImage.src = lazyImage.dataset.src;
                    lazyImage.classList.remove("lazy-load");
                    lazyImageObserver.unobserve(lazyImage);
                }
            });
        });
    
        lazyImages.forEach(function(lazyImage) {
            lazyImageObserver.observe(lazyImage);
        });
    }
    });
    
  3. Минификация и объединение CSS и JavaScript файлов:
     // webpack.mix.js const mix = require('laravel-mix');
    mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')
    .styles([
    'public/css/normalize.css',
    'public/css/main.css',
    'public/css/category.css'
    ], 'public/css/all.css')
    .version();
    
  4. Использование HTTP/2:
     // Nginx configuration server { listen 443 ssl http2; server_name example.com; # ... } 

Улучшение пользовательского опыта

Для повышения удобства использования страницы категории можно внедрить следующие улучшения:

  1. Добавление функции «живого» поиска:
     // resources/views/category/show.blade.php 
    // resources/js/live-search.js
    import axios from 'axios';
    import debounce from 'lodash/debounce';
    
    const liveSearch = document.getElementById('live-search');
    const productsContainer = document.querySelector('.products-container');
    
    const performSearch = debounce((query) => {
    axios.get(/api/search, { params: { query, category_id: categoryId } })
    .then(response => {
    productsContainer.innerHTML = response.data.html;
    })
    .catch(error => {
    console.error('Ошибка при выполнении поиска:', error);
    });
    }, 300);
    
    liveSearch.addEventListener('input', (e) => {
    performSearch(e.target.value);
    });
    
  2. Реализация бесконечной прокрутки:
     // resources/views/category/show.blade.php 
    @foreach($products as $product) @endforeach
    // resources/js/infinite-scroll.js import axios from 'axios'; import throttle from 'lodash/throttle'; const productsContainer = document.querySelector('.products-container'); const loadingSpinner = document.querySelector('.loading-spinner'); let currentPage = 1; const lastPage = parseInt(productsContainer.dataset.lastPage); const loadMoreProducts = throttle(() => { if (currentPage >= lastPage) return; currentPage++; loadingSpinner.style.display = 'block'; axios.get(`${window.location.pathname}?page=${currentPage}`) .then(response => { productsContainer.insertAdjacentHTML('beforeend', response.data.html); loadingSpinner.style.display = 'none'; }) .catch(error => { console.error('Ошибка при загрузке товаров:', error); loadingSpinner.style.display = 'none'; }); }, 1000); window.addEventListener('scroll', () => { if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 500) { loadMoreProducts(); } });
  3. Добавление фильтрации с помощью AJAX:
     // resources/views/category/show.blade.php 
    // resources/js/filter.js import axios from 'axios'; const filterForm = document.getElementById('filter-form'); const productsContainer = document.querySelector('.products-container'); filterForm.addEventListener('submit', function(e) { e.preventDefault(); const formData = new FormData(this); const queryString = new URLSearchParams(formData).toString(); axios.get(`${window.location.pathname}?${queryString}`) .then(response => { productsContainer.innerHTML = response.data.html; history.pushState(null, '', `${window.location.pathname}?${queryString}`); }) .catch(error => { console.error('Ошибка при применении фильтров:', error); }); });

Читайте также  Google начал выделять жирным шрифтом текст в избранных сниппетах
Советы по созданию сайтов