Макеты и наследование
Создание переиспользуемых макетов и наследование шаблонов в 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>© {{ 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 %}
Лучшие практики
Именование блоков
- Используйте описательные имена:
page_content,sidebar_menu - Группируйте блоки логически:
meta,social_meta - Используйте префиксы для специализированных макетов:
admin_sidebar,auth_content
Структурирование блоков
<!-- Хорошо: четкая структура -->
{% 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 %}
Следующий шаг: Изучите макросы и переиспользуемые блоки.