Макеты и наследование

Создание переиспользуемых макетов и наследование шаблонов в MNRFY

Основы наследования

Система наследования позволяет создавать базовые макеты и расширять их в дочерних шаблонах. Макеты хранятся в директории /src/layouts/.

Базовый макет

<!-- /src/layouts/base.html -->
<!DOCTYPE html>
<html lang="{{ locale }}">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
    <!-- Заголовок -->
    <title>{% block title %}{{ config('app.name') }}{% endblock %}</title>
    
    <!-- Мета-теги -->
    {% block meta %}
        <meta name="description" content="{{ config('app.description', '') }}">
        <meta name="keywords" content="{{ config('app.keywords', '') }}">
        <meta name="author" content="{{ config('app.author', '') }}">
        <meta name="robots" content="index, follow">
    {% endblock %}
    
    <!-- Стили -->
    {% block styles %}
        <link rel="stylesheet" href="{{ asset('css/bootstrap.min.css') }}">
        <link rel="stylesheet" href="{{ asset('css/fontawesome.min.css') }}">
        <link rel="stylesheet" href="{{ asset('css/app.css') }}">
    {% endblock %}
    
    <!-- Дополнительные стили для страницы -->
    {% block page_styles %}{% endblock %}
    
    <!-- Мета-теги для соцсетей -->
    {% block social_meta %}
        <meta property="og:title" content="{% block og_title %}{{ config('app.name') }}{% endblock %}">
        <meta property="og:description" content="{% block og_description %}{{ config('app.description') }}{% endblock %}">
        <meta property="og:type" content="{% block og_type %}website{% endblock %}">
        <meta property="og:url" content="{{ current_url() }}">
        <meta property="og:image" content="{% block og_image %}{{ asset('images/og-image.jpg') }}{% endblock %}">
    {% endblock %}
</head>
<body class="{% block body_class %}{% endblock %}">
    <!-- Навигация -->
    {% block navigation %}
        <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
            <div class="container">
                <a class="navbar-brand" href="{{ route('home') }}">
                    {{ config('app.name') }}
                </a>
                
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav">
                    <span class="navbar-toggler-icon"></span>
                </button>
                
                <div class="collapse navbar-collapse" id="navbarNav">
                    <ul class="navbar-nav me-auto">
                        <li class="nav-item">
                            <a class="nav-link {{ is_current_route('home') ? 'active' : '' }}" 
                               href="{{ route('home') }}">
                                {{ t('menu.home') }}
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link {{ is_current_route('about') ? 'active' : '' }}" 
                               href="{{ route('about') }}">
                                {{ t('menu.about') }}
                            </a>
                        </li>
                    </ul>
                    
                    <ul class="navbar-nav">
                        {% if auth() %}
                            <li class="nav-item dropdown">
                                <a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown">
                                    {{ auth().name }}
                                </a>
                                <div class="dropdown-menu">
                                    <a class="dropdown-item" href="{{ route('profile') }}">
                                        {{ t('menu.profile') }}
                                    </a>
                                    <a class="dropdown-item" href="{{ route('settings') }}">
                                        {{ t('menu.settings') }}
                                    </a>
                                    <div class="dropdown-divider"></div>
                                    <a class="dropdown-item" href="{{ route('logout') }}">
                                        {{ t('menu.logout') }}
                                    </a>
                                </div>
                            </li>
                        {% else %}
                            <li class="nav-item">
                                <a class="nav-link" href="{{ route('login') }}">
                                    {{ t('menu.login') }}
                                </a>
                            </li>
                        {% endif %}
                    </ul>
                </div>
            </div>
        </nav>
    {% endblock %}
    
    <!-- Основной контент -->
    <main class="main-content">
        <!-- Уведомления -->
        {% block notifications %}
            {% if flash('success') %}
                <div class="alert alert-success alert-dismissible fade show">
                    {{ flash('success') }}
                    <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                </div>
            {% endif %}
            
            {% if flash('error') %}
                <div class="alert alert-danger alert-dismissible fade show">
                    {{ flash('error') }}
                    <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                </div>
            {% endif %}
            
            {% if flash('warning') %}
                <div class="alert alert-warning alert-dismissible fade show">
                    {{ flash('warning') }}
                    <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
                </div>
            {% endif %}
        {% endblock %}
        
        <!-- Контент страницы -->
        {% block content %}{% endblock %}
    </main>
    
    <!-- Футер -->
    {% block footer %}
        <footer class="bg-dark text-light py-4 mt-5">
            <div class="container">
                <div class="row">
                    <div class="col-md-6">
                        <p>&copy; {{ date('Y') }} {{ config('app.name') }}. {{ t('footer.rights') }}</p>
                    </div>
                    <div class="col-md-6 text-end">
                        <a href="{{ route('privacy') }}" class="text-light me-3">
                            {{ t('footer.privacy') }}
                        </a>
                        <a href="{{ route('terms') }}" class="text-light">
                            {{ t('footer.terms') }}
                        </a>
                    </div>
                </div>
            </div>
        </footer>
    {% endblock %}
    
    <!-- Скрипты -->
    {% block scripts %}
        <script src="{{ asset('js/bootstrap.bundle.min.js') }}"></script>
        <script src="{{ asset('js/app.js') }}"></script>
    {% endblock %}
    
    <!-- Дополнительные скрипты для страницы -->
    {% block page_scripts %}{% endblock %}
</body>
</html>

Использование базового макета

<!-- /src/about.html -->
{% extends 'layouts/base.html' %}

{% block title %}{{ t('pages.about') }} - {{ parent() }}{% endblock %}

{% block meta %}
    {{ parent() }}
    <meta name="description" content="{{ t('pages.about.description') }}">
    <meta name="keywords" content="{{ t('pages.about.keywords') }}">
{% endblock %}

{% block content %}
<div class="container my-5">
    <div class="row">
        <div class="col-lg-8">
            <h1>{{ t('pages.about.title') }}</h1>
            <p class="lead">{{ t('pages.about.lead') }}</p>
            
            <div class="content">
                <h2>{{ t('pages.about.our_story') }}</h2>
                <p>{{ t('pages.about.story_text') }}</p>
                
                <h2>{{ t('pages.about.our_mission') }}</h2>
                <p>{{ t('pages.about.mission_text') }}</p>
            </div>
        </div>
        
        <div class="col-lg-4">
            <div class="sidebar">
                <h3>{{ t('pages.about.quick_facts') }}</h3>
                <ul class="list-unstyled">
                    <li><strong>{{ t('company.founded') }}:</strong> {{ config('company.founded') }}</li>
                    <li><strong>{{ t('company.employees') }}:</strong> {{ config('company.employees') }}+</li>
                    <li><strong>{{ t('company.customers') }}:</strong> {{ config('company.customers') }}+</li>
                </ul>
            </div>
        </div>
    </div>
</div>
{% endblock %}

Специализированные макеты

Макет для авторизации

<!-- /src/layouts/auth.html -->
{% extends 'layouts/base.html' %}

{% block body_class %}auth-layout{% endblock %}

{% block navigation %}
<nav class="navbar navbar-light bg-light">
    <div class="container">
        <a class="navbar-brand" href="{{ route('home') }}">
            {{ config('app.name') }}
        </a>
    </div>
</nav>
{% endblock %}

{% block content %}
<div class="auth-container">
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-6 col-lg-4">
                <div class="auth-card">
                    <div class="auth-header text-center mb-4">
                        {% block auth_header %}
                            <h2>{% block auth_title %}{% endblock %}</h2>
                            {% block auth_subtitle %}{% endblock %}
                        {% endblock %}
                    </div>
                    
                    <div class="auth-body">
                        {% block auth_content %}{% endblock %}
                    </div>
                    
                    <div class="auth-footer text-center mt-4">
                        {% block auth_footer %}{% endblock %}
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}

{% block page_styles %}
<link rel="stylesheet" href="{{ asset('css/auth.css') }}">
{% endblock %}

Использование макета авторизации

<!-- /src/login.html -->
{% extends 'layouts/auth.html' %}

{% block title %}{{ t('auth.login') }} - {{ parent() }}{% endblock %}

{% block auth_title %}{{ t('auth.login') }}{% endblock %}
{% block auth_subtitle %}
    <p class="text-muted">{{ t('auth.login_subtitle') }}</p>
{% endblock %}

{% block auth_content %}
<form method="POST" action="{{ route('login') }}">
    {{ csrf() }}
    
    <div class="mb-3">
        <label for="email" class="form-label">{{ t('form.email') }}</label>
        <input type="email" class="form-control" id="email" name="email" 
               value="{{ old('email') }}" required>
        {% if errors.email %}
            <div class="invalid-feedback d-block">{{ errors.email }}</div>
        {% endif %}
    </div>
    
    <div class="mb-3">
        <label for="password" class="form-label">{{ t('form.password') }}</label>
        <input type="password" class="form-control" id="password" name="password" required>
        {% if errors.password %}
            <div class="invalid-feedback d-block">{{ errors.password }}</div>
        {% endif %}
    </div>
    
    <div class="mb-3 form-check">
        <input type="checkbox" class="form-check-input" id="remember" name="remember">
        <label class="form-check-label" for="remember">
            {{ t('auth.remember_me') }}
        </label>
    </div>
    
    <button type="submit" class="btn btn-primary w-100">
        {{ t('auth.login_button') }}
    </button>
</form>
{% endblock %}

{% block auth_footer %}
    <p>
        {{ t('auth.no_account') }} 
        <a href="{{ route('register') }}">{{ t('auth.register') }}</a>
    </p>
    <p>
        <a href="{{ route('password.request') }}">{{ t('auth.forgot_password') }}</a>
    </p>
{% endblock %}

Макет для административной панели

<!-- /src/layouts/admin.html -->
{% extends 'layouts/base.html' %}

{% block body_class %}admin-layout{% endblock %}

{% block navigation %}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="{{ route('admin.dashboard') }}">
            <i class="fas fa-cogs"></i> {{ t('admin.panel') }}
        </a>
        
        <ul class="navbar-nav ms-auto">
            <li class="nav-item">
                <a class="nav-link" href="{{ route('home') }}" target="_blank">
                    <i class="fas fa-external-link-alt"></i> {{ t('admin.view_site') }}
                </a>
            </li>
            <li class="nav-item dropdown">
                <a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">
                    {{ auth().name }}
                </a>
                <ul class="dropdown-menu">
                    <li><a class="dropdown-item" href="{{ route('profile') }}">{{ t('menu.profile') }}</a></li>
                    <li><hr class="dropdown-divider"></li>
                    <li><a class="dropdown-item" href="{{ route('logout') }}">{{ t('menu.logout') }}</a></li>
                </ul>
            </li>
        </ul>
    </div>
</nav>
{% endblock %}

{% block content %}
<div class="admin-wrapper">
    <!-- Боковая панель -->
    <aside class="admin-sidebar">
        {% block sidebar %}
            <ul class="nav nav-pills nav-sidebar flex-column">
                <li class="nav-item">
                    <a href="{{ route('admin.dashboard') }}" 
                       class="nav-link {{ is_current_route('admin.dashboard') ? 'active' : '' }}">
                        <i class="fas fa-tachometer-alt"></i>
                        {{ t('admin.dashboard') }}
                    </a>
                </li>
                
                <li class="nav-item">
                    <a href="{{ route('admin.users') }}" 
                       class="nav-link {{ is_current_route('admin.users') ? 'active' : '' }}">
                        <i class="fas fa-users"></i>
                        {{ t('admin.users') }}
                        <span class="badge bg-primary">
                            {{ db('1752665380840')->countItems('users', {'ban': 0}) }}
                        </span>
                    </a>
                </li>
                
                <li class="nav-item">
                    <a href="{{ route('admin.posts') }}" 
                       class="nav-link {{ is_current_route('admin.posts') ? 'active' : '' }}">
                        <i class="fas fa-newspaper"></i>
                        {{ t('admin.posts') }}
                    </a>
                </li>
                
                <li class="nav-header">{{ t('admin.system') }}</li>
                
                <li class="nav-item">
                    <a href="{{ route('admin.settings') }}" 
                       class="nav-link {{ is_current_route('admin.settings') ? 'active' : '' }}">
                        <i class="fas fa-cog"></i>
                        {{ t('admin.settings') }}
                    </a>
                </li>
            </ul>
        {% endblock %}
    </aside>
    
    <!-- Основное содержимое -->
    <main class="admin-content">
        <!-- Хлебные крошки -->
        {% block breadcrumbs %}
            {% component 'breadcrumb' with {
                'show_home': false,
                'items': [
                    {'title': t('admin.panel'), 'url': route('admin.dashboard')}
                ] | merge(breadcrumb_items ?? [])
            } %}
        {% endblock %}
        
        <!-- Заголовок страницы -->
        {% block page_header %}
            <div class="page-header d-flex justify-content-between align-items-center mb-4">
                <h1>{% block page_title %}{{ t('admin.panel') }}{% endblock %}</h1>
                <div class="page-actions">
                    {% block page_actions %}{% endblock %}
                </div>
            </div>
        {% endblock %}
        
        <!-- Содержимое страницы -->
        {% block page_content %}{% endblock %}
    </main>
</div>
{% endblock %}

{% block page_styles %}
<link rel="stylesheet" href="{{ asset('css/admin.css') }}">
{% endblock %}

Блоки и их переопределение

Работа с parent()

<!-- Дополнение к родительскому блоку -->
{% block styles %}
    {{ parent() }}  <!-- Включаем стили родителя -->
    <link rel="stylesheet" href="{{ asset('css/custom.css') }}">
{% endblock %}

<!-- Комбинирование с родительским заголовком -->
{% block title %}
    {{ page_title }} - {{ parent() }}
{% endblock %}

Условные блоки

<!-- Условное переопределение навигации -->
{% block navigation %}
    {% if is_mobile() %}
        <!-- Мобильная навигация -->
        <nav class="mobile-nav">
            <button class="menu-toggle">
                <i class="fas fa-bars"></i>
            </button>
            <a class="logo" href="{{ route('home') }}">
                {{ config('app.name') }}
            </a>
        </nav>
    {% else %}
        {{ parent() }}  <!-- Обычная навигация -->
    {% endif %}
{% endblock %}

Сложные макеты

Макет с боковой панелью

<!-- /src/layouts/sidebar.html -->
{% extends 'layouts/base.html' %}

{% block content %}
<div class="container-fluid">
    <div class="row">
        <!-- Боковая панель -->
        <div class="col-md-3 col-lg-2">
            <div class="sidebar">
                {% block sidebar %}
                    <div class="sidebar-section">
                        <h5>{{ t('sidebar.navigation') }}</h5>
                        <ul class="nav nav-pills flex-column">
                            {% block sidebar_menu %}{% endblock %}
                        </ul>
                    </div>
                    
                    {% block sidebar_widgets %}{% endblock %}
                {% endblock %}
            </div>
        </div>
        
        <!-- Основной контент -->
        <div class="col-md-9 col-lg-10">
            <div class="main-content">
                {% block main_content %}{% endblock %}
            </div>
        </div>
    </div>
</div>
{% endblock %}

Использование макета с боковой панелью

<!-- /src/profile.html -->
{% extends 'layouts/sidebar.html' %}

{% block title %}{{ t('profile.title') }} - {{ parent() }}{% endblock %}

{% block sidebar_menu %}
    <li class="nav-item">
        <a class="nav-link {{ is_current_route('profile') ? 'active' : '' }}" 
           href="{{ route('profile') }}">
            <i class="fas fa-user"></i> {{ t('profile.overview') }}
        </a>
    </li>
    <li class="nav-item">
        <a class="nav-link {{ is_current_route('profile.edit') ? 'active' : '' }}" 
           href="{{ route('profile.edit') }}">
            <i class="fas fa-edit"></i> {{ t('profile.edit') }}
        </a>
    </li>
    <li class="nav-item">
        <a class="nav-link {{ is_current_route('profile.security') ? 'active' : '' }}" 
           href="{{ route('profile.security') }}">
            <i class="fas fa-shield-alt"></i> {{ t('profile.security') }}
        </a>
    </li>
    <li class="nav-item">
        <a class="nav-link {{ is_current_route('profile.settings') ? 'active' : '' }}" 
           href="{{ route('profile.settings') }}">
            <i class="fas fa-cog"></i> {{ t('profile.settings') }}
        </a>
    </li>
{% endblock %}

{% block sidebar_widgets %}
    <div class="sidebar-section mt-4">
        <h5>{{ t('profile.stats') }}</h5>
        <div class="stats-widget">
            <div class="stat-item">
                <span class="stat-label">{{ t('profile.member_since') }}:</span>
                <span class="stat-value">{{ auth().created_at | date('F Y') }}</span>
            </div>
            <div class="stat-item">
                <span class="stat-label">{{ t('profile.last_login') }}:</span>
                <span class="stat-value">{{ auth().last_login | time_ago }}</span>
            </div>
        </div>
    </div>
{% endblock %}

{% block main_content %}
    <div class="profile-overview">
        <div class="profile-header">
            <div class="row">
                <div class="col-auto">
                    <img src="{{ auth().avatar ?? asset('images/default-avatar.png') }}" 
                         class="profile-avatar" alt="{{ auth().name }}">
                </div>
                <div class="col">
                    <h2>{{ auth().name }}</h2>
                    <p class="text-muted">@{{ auth().username }}</p>
                    {% if auth().bio %}
                        <p>{{ auth().bio | nl2br }}</p>
                    {% endif %}
                </div>
            </div>
        </div>
        
        <div class="profile-content mt-4">
            <!-- Содержимое профиля -->
        </div>
    </div>
{% endblock %}

Динамические макеты

Выбор макета по условию

<!-- /src/dashboard.html -->
{% if is_mobile() %}
    {% extends 'layouts/mobile.html' %}
{% elseif auth() and auth().role == 'admin' %}
    {% extends 'layouts/admin.html' %}
{% else %}
    {% extends 'layouts/base.html' %}
{% endif %}

{% block title %}Dashboard - {{ parent() }}{% endblock %}

{% block content %}
    <!-- Универсальный контент для всех макетов -->
{% endblock %}

Макет с условными секциями

<!-- /src/layouts/flexible.html -->
{% extends 'layouts/base.html' %}

{% block content %}
<div class="flexible-layout">
    {% if show_header %}
        <header class="page-header">
            {% block page_header %}{% endblock %}
        </header>
    {% endif %}
    
    <div class="layout-container {{ layout_type ?? 'default' }}">
        {% if sidebar_left %}
            <aside class="sidebar-left">
                {% block sidebar_left %}{% endblock %}
            </aside>
        {% endif %}
        
        <main class="main-content">
            {% block main_content %}{% endblock %}
        </main>
        
        {% if sidebar_right %}
            <aside class="sidebar-right">
                {% block sidebar_right %}{% endblock %}
            </aside>
        {% endif %}
    </div>
    
    {% if show_footer_widgets %}
        <section class="footer-widgets">
            {% block footer_widgets %}{% endblock %}
        </section>
    {% endif %}
</div>
{% endblock %}

Лучшие практики

Именование блоков

Структурирование блоков

<!-- Хорошо: четкая структура -->
{% block head %}
    {% block meta %}{% endblock %}
    {% block styles %}{% endblock %}
    {% block page_styles %}{% endblock %}
{% endblock %}

{% block body %}
    {% block navigation %}{% endblock %}
    {% block content %}{% endblock %}
    {% block footer %}{% endblock %}
    {% block scripts %}{% endblock %}
    {% block page_scripts %}{% endblock %}
{% endblock %}

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

<!-- Условная загрузка ресурсов -->
{% block page_styles %}
    {% if load_datatables %}
        <link rel="stylesheet" href="{{ asset('css/datatables.min.css') }}">
    {% endif %}
    {% if load_charts %}
        <link rel="stylesheet" href="{{ asset('css/charts.min.css') }}">
    {% endif %}
{% endblock %}

{% block page_scripts %}
    {% if load_datatables %}
        <script src="{{ asset('js/datatables.min.js') }}"></script>
    {% endif %}
    {% if load_charts %}
        <script src="{{ asset('js/chart.min.js') }}"></script>
    {% endif %}
{% endblock %}
Следующий шаг: Изучите макросы и переиспользуемые блоки.