Laravel: Laravel视图与Blade模板引擎

最后更新:2026-08-26

Blade 是 Laravel 的"排版车间"——数据在这里变成用户看到的页面,布局、组件、继承让页面不再重复。

1. 你将学到


2. 一个前端开发者的真实故事

(1) 痛点:每个页面都重复写导航栏

Charlie 为 ShopMetrics 写了 10 个页面,每个页面都手动复制了 60 行的导航栏 HTML。当 Bob 要求把"定价"链接从导航栏移到下拉菜单时,Charlie 不得不逐个修改 10 个文件——改到第 7 个时漏了一个 class,线上导航栏样式不一致,被 Alice 投诉了 3 次。

(2) Blade 布局的解法

Blade 用 @extends 继承布局,@section 填充内容,导航栏只写一次,全站自动同步。

PHP
// resources/views/layouts/app.blade.php — Define layout once
<body>
    <nav>...</nav>  <!-- Navigation appears on every page -->
    @yield('content')  <!-- Each page fills this slot -->
</body>

// resources/views/shops/index.blade.php — Extend and fill
@extends('layouts.app')
@section('content')
    <h1>All Shops</h1>
@endsection

(3) 收益

Charlie 用 Blade 布局后,导航栏只需维护 1 个文件,Bob 的修改请求从"改 10 个文件"变成"改 1 个文件",0 遗漏。


3. Blade 布局继承

(1) 定义布局

HTML
<!-- resources/views/layouts/app.blade.php -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>@yield('title', 'ShopMetrics')</title>
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
    @include('partials.navbar')
    <main class="container mx-auto">
        @yield('content')
    </main>
    @include('partials.footer')
</body>
</html>

(2) 继承布局

HTML
<!-- resources/views/shops/index.blade.php -->
@extends('layouts.app')

@section('title', 'All Shops')

@section('content')
    <div class="grid grid-cols-3 gap-4">
        @foreach($shops as $shop)
            <div class="card">{{ $shop->name }}</div>
        @endforeach
    </div>
@endsection
指令 作用 类比
@extends 指定继承的布局 选择模板
@section 定义内容块 填充模板中的空位
@yield 输出内容块 模板中的空位
@include 引入子视图 插入公共片段

▶ 示例:ShopMetrics 双栏布局

HTML
<!-- resources/views/layouts/dashboard.blade.php -->
@extends('layouts.app')

@section('content')
    <div class="flex">
        @include('partials.sidebar')
        <div class="flex-1 p-6">
            @yield('dashboard-content')
        </div>
    </div>
@endsection

<!-- resources/views/dashboard/analytics.blade.php -->
@extends('layouts.dashboard')

@section('title', 'Analytics Dashboard')

@section('dashboard-content')
    <h1>Analytics Overview</h1>
    <div id="chart"></div>
@endsection

输出:

TEXT 📖 仅展示
// 执行成功

4. 控制结构

(1) 条件指令

PHP
@if($shop->is_active)
    <span class="badge-green">Active</span>
@elseif($shop->is_suspended)
    <span class="badge-yellow">Suspended</span>
@else
    <span class="badge-gray">Inactive</span>
@endif

@auth
    <a href="{{ route('dashboard') }}">Dashboard</a>
@endauth

@guest
    <a href="{{ route('login') }}">Login</a>
@endguest

(2) 循环指令

PHP
@foreach($shops as $shop)
    <tr>
        <td>{{ $shop->name }}</td>
        <td>{{ $shop->revenue }}</td>
    </tr>
    @if($loop->last)
        </tbody></table>
    @endif
@endforeach

@forelse($orders as $order)
    <li>{{ $order->total }}</li>
@empty
    <li>No orders yet.</li>
@endforelse
循环变量 说明
$loop->index 当前索引(从 0 开始)
$loop->iteration 当前轮次(从 1 开始)
$loop->first 是否第一个
$loop->last 是否最后一个
$loop->count 总数

▶ 示例:ShopMetrics 仪表盘条件渲染

HTML
<!-- resources/views/dashboard/index.blade.php -->
@extends('layouts.dashboard')

@section('dashboard-content')
    @auth
        <h1>Welcome, {{ auth()->user()->name }}!</h1>
    @endauth

    @if($shops->count() > 0)
        <div class="grid grid-cols-3 gap-4">
            @foreach($shops as $shop)
                <div class="card {{ $loop->first ? 'border-blue-500' : '' }}">
                    <h3>{{ $shop->name }}</h3>
                    <p>Revenue: ${{ number_format($shop->revenue, 2) }}</p>
                </div>
            @endforeach
        </div>
    @else
        <p>No shops yet. <a href="{{ route('shops.create') }}">Create one!</a></p>
    @endif
@endsection

输出:

TEXT 📖 仅展示
// 执行成功

5. Blade 组件

(1) Anonymous Components

HTML
<!-- resources/views/components/shop-card.blade.php -->
@props(['shop', 'highlight' => false])

<div class="card {{ $highlight ? 'border-gold' : '' }}">
    <h3>{{ $shop->name }}</h3>
    <p>{{ $shop->description }}</p>
    {{ $slot }}
</div>

<!-- Usage -->
<x-shop-card :shop="$shop" :highlight="true">
    <p>Featured this week!</p>
</x-shop-card>

(2) Class Components

BASH
php artisan make:component Alert
# Creates: app/View/Components/Alert.php
# And:     resources/views/components/alert.blade.php
PHP
// app/View/Components/Alert.php
class Alert extends Component
{
    public function __construct(
        public string $type = 'info',
        public ?string $message = null,
    ) {}

    public function render(): View
    {
        return view('components.alert');
    }
}
HTML
<!-- resources/views/components/alert.blade.php -->
<div class="alert alert-{{ $type }}">
    {{ $message ?? $slot }}
</div>

<!-- Usage -->
<x-alert type="success" message="Shop created!" />
<x-alert type="warning">Check your settings.</x-alert>
维度 Anonymous Class
文件 仅 Blade 模板 PHP 类 + Blade 模板
逻辑 无/极简 可包含业务逻辑
Props @props() 声明 构造函数参数
适合 简单展示组件 需要逻辑的组件

▶ 示例:ShopMetrics 统计卡片组件

HTML
<!-- resources/views/components/stat-card.blade.php -->
@props(['label', 'value', 'icon', 'trend' => null])

<div class="bg-white rounded-lg shadow p-6">
    <div class="flex items-center justify-between">
        <div>
            <p class="text-gray-500 text-sm">{{ $label }}</p>
            <p class="text-2xl font-bold">{{ $value }}</p>
            @if($trend)
                <p class="text-sm {{ $trend > 0 ? 'text-green-500' : 'text-red-500' }}">
                    {{ $trend > 0 ? '+' : '' }}{{ $trend }}%
                </p>
            @endif
        </div>
        <span class="text-3xl">{{ $icon }}</span>
    </div>
</div>

<!-- Usage in dashboard -->
<x-stat-card label="Total Revenue" value="$12,450" icon="$" :trend="15.3" />
<x-stat-card label="Active Shops" value="23" icon="#" :trend="-2.1" />

输出:

TEXT 📖 仅展示
// 执行成功

6. 视图合成器

View Composers 自动为视图注入共享数据,避免在每个控制器方法中重复传递。

PHP
// app/Providers/AppServiceProvider.php
public function boot(): void
{
    // Share tenant info with all dashboard views
    View::composer('dashboard.*', function ($view) {
        $view->with('tenant', Tenant::current());
    });

    // Share navigation data with specific views
    View::composer(['layouts.app', 'shops.*'], NavigationComposer::class);
}
类型 何时执行 用途
View::composer() 每次渲染视图时 动态计算数据
View::creator() 视图实例化时 更早绑定数据
View::share() 所有视图 全局常量

▶ 示例:ShopMetrics 导航视图合成器

PHP
// app/View/Composers/NavigationComposer.php
class NavigationComposer
{
    public function compose(View $view): void
    {
        $view->with([
            'navShops' => auth()->check()
                ? auth()->user()->shops()->take(5)->get()
                : collect(),
            'navNotifications' => auth()->check()
                ? auth()->user()->unreadNotifications()->count()
                : 0,
        ]);
    }
}

// Register in AppServiceProvider
View::composer('layouts.app', NavigationComposer::class);

输出:

TEXT 📖 仅展示
// 执行成功

7. 前端资产:Vite

Laravel 11 默认使用 Vite 编译前端资产。

(1) 安装 Tailwind CSS 和 Alpine.js

BASH
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
npm install alpinejs

(2) 配置 Vite

JAVASCRIPT
// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/js/app.js',
            ],
            refresh: true,
        }),
    ],
});

(3) Blade 中引入

HTML
<!-- In layout head -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
命令 作用
npm run dev 启动 Vite 开发服务器(热更新)
npm run build 编译生产资产(压缩)

▶ 示例:ShopMetrics 带 Alpine.js 交互的导航

HTML
<!-- resources/views/partials/navbar.blade.php -->
<nav class="bg-white shadow" x-data="{ open: false }">
    <div class="container mx-auto px-4">
        <div class="flex justify-between h-16">
            <a href="{{ route('home') }}" class="font-bold text-xl">ShopMetrics</a>

            <div class="flex items-center">
                @auth
                    <div class="relative" @click="open = !open">
                        <button class="flex items-center">
                            {{ auth()->user()->name }}
                            @if($navNotifications > 0)
                                <span class="badge-red">{{ $navNotifications }}</span>
                            @endif
                        </button>
                        <div x-show="open" x-transition class="dropdown">
                            <a href="{{ route('dashboard') }}">Dashboard</a>
                            <a href="{{ route('shops.index') }}">Shops</a>
                            <form action="{{ route('logout') }}" method="POST">
                                @csrf
                                <button type="submit">Logout</button>
                            </form>
                        </div>
                    </div>
                @else
                    <a href="{{ route('login') }}">Login</a>
                @endauth
            </div>
        </div>
    </div>
</nav>

输出:

TEXT 📖 仅展示
// 执行成功

8. 综合示例:ShopMetrics 仪表盘页面

HTML
<!-- ============================================
     Comprehensive: ShopMetrics Dashboard Page
     Covers: layout inheritance, components, directives, Alpine.js
     ============================================ -->

<!-- resources/views/dashboard/index.blade.php -->
@extends('layouts.dashboard')

@section('title', 'Dashboard — ShopMetrics')

@section('dashboard-content')
    <div class="space-y-6" x-data="{ period: '7d' }">
        <!-- Period Selector -->
        <div class="flex gap-2">
            <button @click="period = '7d'"
                    :class="period === '7d' ? 'btn-primary' : 'btn-secondary'">7 Days</button>
            <button @click="period = '30d'"
                    :class="period === '30d' ? 'btn-primary' : 'btn-secondary'">30 Days</button>
            <button @click="period = '90d'"
                    :class="period === '90d' ? 'btn-primary' : 'btn-secondary'">90 Days</button>
        </div>

        <!-- Stat Cards -->
        <div class="grid grid-cols-4 gap-4">
            <x-stat-card label="Total Revenue" value="${{ number_format($totalRevenue, 0) }}" icon="$" :trend="$revenueTrend" />
            <x-stat-card label="Orders" value="{{ number_format($orderCount) }}" icon="#" :trend="$orderTrend" />
            <x-stat-card label="Active Shops" value="{{ $activeShops }}" icon="#" :trend="$shopTrend" />
            <x-stat-card label="Conversion Rate" value="{{ number_format($conversionRate, 1) }}%" icon="%" :trend="$conversionTrend" />
        </div>

        <!-- Recent Orders -->
        <div class="bg-white rounded-lg shadow">
            <h2 class="p-4 border-b font-semibold">Recent Orders</h2>
            @forelse($recentOrders as $order)
                <div class="p-4 border-b flex justify-between">
                    <span>{{ $order->product_name }}</span>
                    <span>${{ number_format($order->total, 2) }}</span>
                </div>
            @empty
                <p class="p-4 text-gray-500">No orders in this period.</p>
            @endforelse
        </div>
    </div>
@endsection

❓ 常见问题

Q Blade 的 {{ }} 和 {!! !!} 有什么区别?
A {{ }} 会自动转义 HTML(防 XSS),{!! !!} 输出原始 HTML 不转义。只在信任内容时用 {!! !!},如富文本编辑器输出。
Q @include 和 @component 有什么区别?
A @include 是简单的视图片段引入,没有 props 隔离;@component 有明确的 props 声明和 slot 机制,更适合可复用的 UI 组件。简单片段用 @include,独立组件用 @component。
Q Blade 组件和 Vue 组件有什么区别?
A Blade 组件在服务端渲染,生成静态 HTML;Vue 组件在客户端渲染,支持响应式数据绑定。Laravel 项目通常 Blade 做页面结构,Alpine.js 或 Vue 做交互。
Q Vite 和 Mix 有什么区别?
A Vite 是 Laravel 9+ 默认的前端构建工具,使用 ES Module 开发服务器,启动快热更新快;Mix(Laravel Mix)基于 Webpack,配置简单但速度慢。新项目用 Vite。
Q 如何在 Blade 中调试变量?
A{{ dd($variable) }} 直接在页面 dump 并终止;或安装 Laravel Debugbar 扩展包查看所有变量、SQL 查询和内存使用。
Q 视图合成器和控制器传数据有什么区别?
A 控制器用 view('xxx', ['key' => $value]) 显式传数据,适合该视图专有的数据;视图合成器自动注入共享数据,适合多个视图都需要的数据(如导航栏、通知数)。

📖 小节


📝 作业

  1. 基础题(⭐):为 ShopMetrics 创建 layouts/app.blade.php 布局文件,包含导航栏和页脚,然后创建首页 home/index.blade.php 继承该布局并填充内容。

  2. 进阶题(⭐⭐):创建一个 <x-alert> Blade 组件,支持 type(success/warning/error)和 slot 内容,在 ShopMetrics 的创建成功/验证失败场景中使用。

  3. 挑战题(⭐⭐⭐):使用 Alpine.js 实现一个可折叠的侧边栏组件,在仪表盘布局中集成,支持移动端自动折叠,状态保存到 localStorage。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏