Laravel 10 Crud和图像上传教程,带有Laravel微风和回购示例
#php #laravel #authentication #crud

Laravel 10 CRUD and image upload illustration

使用微风构建带有图像上传的Laravel 10 Crud应用程序 - 逐步教程与repo示例

Laravel是一个流行的开源PHP框架,可轻松构建Web应用程序。它为Web开发人员提供了一组强大的工具和功能,例如路由,中间件,数据库集成和模板。

Laravel 10是Laravel的最新版本,于2023年发行。它具有几个新功能和改进,例如对PHP 8.1的支持,增强的安全功能和性能优化。

在本教程中,我们将使用最流行的PHP框架之一Laravel创建一个简单的CRUD(创建,阅读,更新,删除)应用程序。 CRUD应用程序将使我们能够管理博客文章列表,包括创建新帖子,阅读现有帖子,更新帖子和删除帖子。除此之外,我们还将学习如何上传和管理博客文章的图像。

在本教程结束时,您将对如何在Laravel中构建基本的CRUD应用程序以及如何与应用程序中的图像一起工作有很好的了解。让我们开始!

安装Laravel 10

在开始之前,请确保您在系统上安装了最新版本的PHP(至少PHP 8.1)和作曲家。要安装Laravel 10,您可以使用以下命令:

composer create-project --prefer-dist laravel/laravel:^10 pmid-laravel10-breeze

此命令将在名为pmid-laravel10-breeze的目录中创建一个新的Laravel 10项目。

初始化数据库

接下来,我们需要设置一个数据库。对于本教程,创建一个名为pmid_laravel10_breeze的数据库。创建数据库后,我们需要在Laravel应用程序的.env文件中修改数据库连接设置,以匹配新的数据库名称以及用户名和密码。

安装Laravel微风

Laravel Breeze是Laravel 8及更高版本的轻量级身份验证脚手架。它提供了一种简单的方法,可以在Laravel应用程序中添加身份验证功能。

要安装Laravel Breeze,在您的终端中运行以下命令:

composer require laravel/breeze --dev

通过作曲家下载Laravel Breeze后,我们可以运行breeze:install Artisan命令在我们的Laravel 10应用程序中安装Laravel Breeze:

php artisan breeze:install

将出现一个提示,询问我们要安装哪个堆栈。选择“刀片”。可能会出现另一个提示,询问我们是否要启用暗模式支持。目前,我们不会使用它,所以回答“不”。最后,可能会出现提示,询问我们是否要使用害虫测试框架。目前,我们不会使用它,所以回答“否”。

创建帖子迁移

现在我们已经安装了Laravel 10和Laravel Breeze,让我们为posts表创建一个迁移。该表将存储我们的CRUD教程的帖子数据。

要创建迁移,请在终端中运行以下命令:

php artisan make:migration "create posts table"

此命令将在database/migrations目录中创建一个新的迁移文件。

打开迁移文件并添加以下代码以创建posts表:

public function up()
{
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->text('content');
        $table->text('featured_image');
        $table->timestamps();
    });
}

public function down()
{
    Schema::dropIfExists('posts');
}

此代码将使用idtitlecontentfeatured_image和时间戳列创建一个邮票表。

迁移迁移

最后,让我们运行迁移以在数据库中创建posts表。为此,请在终端中运行以下命令:

php artisan migrate

此命令将运行database/migrations目录中的所有未出色迁移。

创建路线

现在我们已经设置了posts表和数据库,让我们创建一些路由来处理博客文章的CRUD操作。

打开routes/web.php文件并使用以下代码进行更新:

<?php

use App\Http\Controllers\ProfileController;
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');

Route::middleware('auth')->group(function () {
    Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
    Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
    Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');

    // Add the following route to the existing routes because we want the posts route accessible to authenticated users only.
    // We'll use a resource route because it contains all the exact routes we need for a typical CRUD application.
    Route::resource('posts', PostController::class);
});

require __DIR__.'/auth.php';

这将发布资源路由在现有路由中添加了,并应用了验证中间件以限制对身份验证的用户的访问。 PostController作为资源控制器设置为处理所有典型的CRUD操作。

另请阅读:

创建PostController

现在,让我们创建一个后控制器来处理我们的博客文章的CRUD操作。要使用资源方法创建新的控制器,请在终端中运行以下命令:

php artisan make:controller PostController --resource

此命令将创建一个新的PostController,其中包含app/Http/Controllers目录中的所有资源方法。

打开PostController并将以下代码添加到类:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Storage;
// Use the Post Model
use App\Models\Post;
// We will use Form Request to validate incoming requests from our store and update method
use App\Http\Requests\Post\StoreRequest;
use App\Http\Requests\Post\UpdateRequest;

class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index(): Response
    {
        return response()->view('posts.index', [
            'posts' => Post::orderBy('updated_at', 'desc')->get(),
        ]);
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create(): Response
    {
        return response()->view('posts.form');
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(StoreRequest $request): RedirectResponse
    {
        $validated = $request->validated();

        if ($request->hasFile('featured_image')) {
            // put image in the public storage
            $file = Storage::disk('public')->put('images/posts/featured-images', request()->file('featured_image'), 'public');
            // get the image path in the url
            $path = Storage::url($file);
            $validated['featured_image'] = $path;
        }

        // insert only requests that already validated in the StoreRequest
        $create = Post::create($validated);

        if($create) {
            // add flash for the success notification
            session()->flash('notif.success', 'Post created successfully!');
            return redirect()->route('posts.index');
        }

        return abort(500);
    }

    /**
     * Display the specified resource.
     */
    public function show(string $id): Response
    {
        return response()->view('posts.show', [
            'post' => Post::findOrFail($id),
        ]);
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(string $id): Response
    {
        return response()->view('posts.form', [
            'post' => Post::findOrFail($id),
        ]);
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(UpdateRequest $request, string $id): RedirectResponse
    {
        $post = Post::findOrFail($id);
        $validated = $request->validated();

        if ($request->hasFile('featured_image')) {
            // get current image path and replace the storage path with public path
            $currentImage = str_replace('/storage', '/public', $post->featured_image);
            // delete current image
            Storage::delete($currentImage);

            $file = Storage::disk('public')->put('images/posts/featured-images', request()->file('featured_image'), 'public');
            $path = Storage::url($file);
            $validated['featured_image'] = $path;
        }

        $update = $post->update($validated);

        if($update) {
            session()->flash('notif.success', 'Post updated successfully!');
            return redirect()->route('posts.index');
        }

        return abort(500);
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(string $id): RedirectResponse
    {
        $post = Post::findOrFail($id);

        $currentImage = str_replace('/storage', '/public', $post->featured_image);
        Storage::delete($currentImage);

        $delete = $post->delete($id);

        if($delete) {
            session()->flash('notif.success', 'Post deleted successfully!');
            return redirect()->route('posts.index');
        }

        return abort(500);
    }
}

创建对Store()方法的表单请求

我们可以运行以下命令来为store()创建表单请求:

php artisan make:request Post/StoreRequest

,然后将以下代码添加到类:

    public function authorize(): bool
    {
        // dont' forget to set this as true
        return true;
    }

    public function rules(): array
    {
        // make all of the fields required, set featured image to accept only images
        return [
            'title' => 'required|string|min:3|max:250',
            'content' => 'required|string|min:3|max:6000',
            'featured_image' => 'required|image|max:1024|mimes:jpg,jpeg,png',
        ];
    }

创建更新()方法的表单请求

我们可以运行以下命令来为update()创建表单请求:

php artisan make:request Post/UpdateRequest

此类的代码类似于StoreRequest的代码,但featured_image规则设置为无效:

    public function authorize(): bool
    {
        // dont' forget to set this as true
        return true;
    }

    public function rules(): array
    {
        // make all of the fields required, set featured image to accept only images
        return [
            'title' => 'required|string|min:3|max:250',
            'content' => 'required|string|min:3|max:6000',
            'featured_image' => 'nullable|image|max:1024|mimes:jpg,jpeg,png',
        ];
    }

添加$填充到帖子模型

app/Models/Post.php文件中,添加可填充属性以指定可以质量分配的字段。这是一个例子:

class Post extends Model
{
    protected $fillable = [
        'title', 
        'content',
        'featured_image',
    ];
}

为我们的公共存储创建符号链接

要使可以从Web访问的公共磁盘中存储文件,需要从public/storage文件夹创建一个符号链接到storage/app/public文件夹。默认情况下,公共磁盘使用本地驱动程序。我们的公共存储配置不需要更改。要创建符号链接,请运行以下命令:

php artisan storage:link

tailwindcss和Vite配置

我们将使用tailwindcss进行样式和Vite作为我们的资产捆绑助手。因此,我们需要稍微调整它们的配置。

由于我们将为应用程序编写其他样式,因此我们需要在编码视图时启动尾风CLI构建过程。为此,运行以下命令:

npx tailwindcss -i ./resources/css/app.css -o ./resources/css/main.css --watch

此命令将在resources/css/main.css上生成一个新文件。现在,我们需要告诉Vite我们将使用main.css而不是app.css

我们将重复使用resources/views/layouts/app.blade.php布局,因此在“头部”部分中将VITE代码更新到以下:

    {{-- some other code --}}
    @vite(['resources/css/main.css', 'resources/js/app.js'])
</head>

设置布局

我们将更新布局主体内部的标题部分,以便可以处理成功消息的闪存通知。请使用以下代码进行更新:

<!-- Page Heading -->
@if (isset($header))
    <header class="bg-white shadow">
        <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
            {{ $header }}

            {{-- check if there is a notif.success flash session --}}
            @if (Session::has('notif.success'))
            <div class="bg-blue-300 mt-2 p-4">
                {{-- if it's there then print the notification --}}
                <span class="text-white">{{ Session::get('notif.success') }}</span>
            </div>
            @endif
        </div>
    </header>
@endif

因此,我们的资源\ views \ layouts \ app.blade.php文件将使用以下代码更新:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="csrf-token" content="{{ csrf_token() }}">

        <title>{{ config('app.name', 'Laravel') }}</title>

        <!-- Fonts -->
        <link rel="preconnect" href="https://fonts.bunny.net">
        <link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />

        <!-- Scripts -->
        @vite(['resources/css/main.css', 'resources/js/app.js'])
    </head>
    <body class="font-sans antialiased">
        <div class="min-h-screen bg-gray-100">
            @include('layouts.navigation')

            <!-- Page Heading -->
            @if (isset($header))
                <header class="bg-white shadow">
                    <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
                        {{ $header }}

                        @if (Session::has('notif.success'))
                        <div class="bg-blue-300 mt-2 p-4">
                            <span class="text-white">{{ Session::get('notif.success') }}</span>
                        </div>
                        @endif
                    </div>
                </header>
            @endif

            <!-- Page Content -->
            <main>
                {{ $slot }}
            </main>
        </div>
    </body>
</html>

另请阅读:

创建索引视图

创建一个带有以下内容的新文件resources/views/posts/index.blade.php

{{-- use AppLayout Component located in app\View\Components\AppLayout.php which use resources\views\layouts\app.blade.php view --}}
<x-app-layout>
    <x-slot name="header">
        <div class="flex justify-between">
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                {{ 'Posts' }}
            </h2>
            <a href="{{ route('posts.create') }}" class="bg-blue-500 text-white px-4 py-2 rounded-md">ADD</a>
        </div>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    <table class="border-collapse table-auto w-full text-sm">
                        <thead>
                            <tr>
                                <th class="border-b font-medium p-4 pl-8 pt-0 pb-3 text-slate-400 text-left">Title</th>
                                <th class="border-b font-medium p-4 pl-8 pt-0 pb-3 text-slate-400 text-left">Created At</th>
                                <th class="border-b font-medium p-4 pl-8 pt-0 pb-3 text-slate-400 text-left">Updated At</th>
                                <th class="border-b font-medium p-4 pl-8 pt-0 pb-3 text-slate-400 text-left">Action</th>
                            </tr>
                        </thead>
                        <tbody class="bg-white">
                            {{-- populate our post data --}}
                            @foreach ($posts as $post)
                                <tr>
                                    <td class="border-b border-slate-100 dark:border-slate-700 p-4 pl-8 text-slate-500 dark:text-slate-400">{{ $post->title }}</td>
                                    <td class="border-b border-slate-100 dark:border-slate-700 p-4 pl-8 text-slate-500 dark:text-slate-400">{{ $post->created_at }}</td>
                                    <td class="border-b border-slate-100 dark:border-slate-700 p-4 pl-8 text-slate-500 dark:text-slate-400">{{ $post->updated_at }}</td>
                                    <td class="border-b border-slate-100 dark:border-slate-700 p-4 pl-8 text-slate-500 dark:text-slate-400">
                                        <a href="{{ route('posts.show', $post->id) }}" class="border border-blue-500 hover:bg-blue-500 hover:text-white px-4 py-2 rounded-md">SHOW</a>
                                        <a href="{{ route('posts.edit', $post->id) }}" class="border border-yellow-500 hover:bg-yellow-500 hover:text-white px-4 py-2 rounded-md">EDIT</a>
                                        {{-- add delete button using form tag --}}
                                        <form method="post" action="{{ route('posts.destroy', $post->id) }}" class="inline">
                                            @csrf
                                            @method('delete')
                                            <button class="border border-red-500 hover:bg-red-500 hover:text-white px-4 py-2 rounded-md">DELETE</button>
                                        </form>
                                    </td>
                                </tr>
                            @endforeach
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

创建显示视图

创建一个带有以下内容的新文件resources/views/posts/show.blade.php

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ 'Show' }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    <div class="mb-6">
                        <h2 class="text-lg font-medium text-gray-900">
                            {{ 'Title' }}
                        </h2>

                        <p class="mt-1 text-sm text-gray-600">
                            {{ $post->title }}
                        </p>
                    </div>
                    <div class="mb-6">
                        <h2 class="text-lg font-medium text-gray-900">
                            {{ 'Content' }}
                        </h2>

                        <p class="mt-1 text-sm text-gray-600">
                            {{ $post->content }}
                        </p>
                    </div>
                    <div class="mb-6">
                        <h2 class="text-lg font-medium text-gray-900">
                            {{ 'Featured Image' }}
                        </h2>

                        <p class="mt-1 text-sm text-gray-600">
                            <img class="h-64 w-128" src="{{ asset($post->featured_image) }}" alt="{{ $post->title }}" srcset="">
                        </p>
                    </div>
                    <div class="mb-6">
                        <h2 class="text-lg font-medium text-gray-900">
                            {{ 'Created At' }}
                        </h2>

                        <p class="mt-1 text-sm text-gray-600">
                            {{ $post->created_at }}
                        </p>
                    </div>
                    <div class="mb-6">
                        <h2 class="text-lg font-medium text-gray-900">
                            {{ 'Updated At' }}
                        </h2>

                        <p class="mt-1 text-sm text-gray-600">
                            {{ $post->updated_at }}
                        </p>
                    </div>
                    <a href="{{ route('posts.index') }}" class="bg-blue-500 text-white px-4 py-2 rounded-md">BACK</a>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

创建表单视图

创建一个新的文件resources/views/posts/form.blade.php。在这里,我们将使用一些已经从Laravel Breeze获得的已经定义的组件。编写以下代码:

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{-- Use 'Edit' for edit mode and create for non-edit/create mode --}}
            {{ isset($post) ? 'Edit' : 'Create' }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    {{-- don't forget to add multipart/form-data so we can accept file in our form --}}
                    <form method="post" action="{{ isset($post) ? route('posts.update', $post->id) : route('posts.store') }}" class="mt-6 space-y-6" enctype="multipart/form-data">class="mt-6 space-y-6">
                        @csrf
                        {{-- add @method('put') for edit mode --}}
                        @isset($post)
                            @method('put')
                        @endisset

                        <div>
                            <x-input-label for="title" value="Title" />
                            <x-text-input id="title" name="title" type="text" class="mt-1 block w-full" :value="$post->title ?? old('title')" required autofocus />
                            <x-input-error class="mt-2" :messages="$errors->get('title')" />
                        </div>

                        <div>
                            <x-input-label for="content" value="Content" />
                            {{-- use textarea-input component that we will create after this --}}
                            <x-textarea-input id="content" name="content" class="mt-1 block w-full" required autofocus>{{ $post->content ?? old('content') }}</x-textarea-input>
                            <x-input-error class="mt-2" :messages="$errors->get('content')" />
                        </div>

                        <div>
                            <x-input-label for="featured_image" value="Featured Image" />
                            <label class="block mt-2">
                                <span class="sr-only">Choose image</span>
                                <input type="file" id="featured_image" name="featured_image" class="block w-full text-sm text-slate-500
                                    file:mr-4 file:py-2 file:px-4
                                    file:rounded-full file:border-0
                                    file:text-sm file:font-semibold
                                    file:bg-violet-50 file:text-violet-700
                                    hover:file:bg-violet-100
                                "/>
                            </label>
                            <div class="shrink-0 my-2">
                                <img id="featured_image_preview" class="h-64 w-128 object-cover rounded-md" src="{{ isset($post) ? asset($post->featured_image) : '' }}" alt="Featured image preview" />
                            </div>
                            <x-input-error class="mt-2" :messages="$errors->get('featured_image')" />
                        </div>

                        <div class="flex items-center gap-4">
                            <x-primary-button>{{ __('Save') }}</x-primary-button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
    <script>
        // create onchange event listener for featured_image input
        document.getElementById('featured_image').onchange = function(evt) {
            const [file] = this.files
            if (file) {
                // if there is an image, create a preview in featured_image_preview
                document.getElementById('featured_image_preview').src = URL.createObjectURL(file)
            }
        }
    </script>
</x-app-layout>

创建Textarea输入组件

使用以下代码创建其他组件:

@props(['disabled' => false])

<textarea {{ $disabled ? 'disabled' : '' }} {!! $attributes->merge(['class' => 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm']) !!}>{{$slot}}</textarea>

更新导航组件

使用以下代码更新导航组件:

<nav x-data="{ open: false }" class="bg-white border-b border-gray-100">
    <!-- Primary Navigation Menu -->
    <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div class="flex justify-between h-16">
            <div class="flex">
                <!-- Logo -->
                <div class="shrink-0 flex items-center">
                    <a href="{{ route('dashboard') }}">
                        <x-application-logo class="block h-9 w-auto fill-current text-gray-800" />
                    </a>
                </div>

                <!-- Navigation Links -->
                <div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
                    <x-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
                        {{ __('Dashboard') }}
                    </x-nav-link>
                    <!-- add this -->
                    <x-nav-link :href="route('posts.index')" :active="request()->routeIs('posts.index')">
                        {{'Posts' }}
                    </x-nav-link>
                </div>
            </div>

            <!-- Settings Dropdown -->
            <div class="hidden sm:flex sm:items-center sm:ml-6">
                <x-dropdown align="right" width="48">
                    <x-slot name="trigger">
                        <button class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none transition ease-in-out duration-150">
                            <div>{{ Auth::user()->name }}</div>

                            <div class="ml-1">
                                <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
                                    <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
                                </svg>
                            </div>
                        </button>
                    </x-slot>

                    <x-slot name="content">
                        <x-dropdown-link :href="route('profile.edit')">
                            {{ __('Profile') }}
                        </x-dropdown-link>

                        <!-- Authentication -->
                        <form method="POST" action="{{ route('logout') }}">
                            @csrf

                            <x-dropdown-link :href="route('logout')"
                                    onclick="event.preventDefault();
                                                this.closest('form').submit();">
                                {{ __('Log Out') }}
                            </x-dropdown-link>
                        </form>
                    </x-slot>
                </x-dropdown>
            </div>

            <!-- Hamburger -->
            <div class="-mr-2 flex items-center sm:hidden">
                <button @click="open = ! open" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 focus:text-gray-500 transition duration-150 ease-in-out">
                    <svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24">
                        <path :class="{'hidden': open, 'inline-flex': ! open }" class="inline-flex" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
                        <path :class="{'hidden': ! open, 'inline-flex': open }" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                    </svg>
                </button>
            </div>
        </div>
    </div>

    <!-- Responsive Navigation Menu -->
    <div :class="{'block': open, 'hidden': ! open}" class="hidden sm:hidden">
        <div class="pt-2 pb-3 space-y-1">
            <x-responsive-nav-link :href="route('dashboard')" :active="request()->routeIs('dashboard')">
                {{ __('Dashboard') }}
            </x-responsive-nav-link>
            <!-- add this -->
            <x-responsive-nav-link :href="route('posts.index')" :active="request()->routeIs('posts.index')">
                {{ 'Posts' }}
            </x-responsive-nav-link>
        </div>

        <!-- Responsive Settings Options -->
        <div class="pt-4 pb-1 border-t border-gray-200">
            <div class="px-4">
                <div class="font-medium text-base text-gray-800">{{ Auth::user()->name }}</div>
                <div class="font-medium text-sm text-gray-500">{{ Auth::user()->email }}</div>
            </div>

            <div class="mt-3 space-y-1">
                <x-responsive-nav-link :href="route('profile.edit')">
                    {{ __('Profile') }}
                </x-responsive-nav-link>

                <!-- Authentication -->
                <form method="POST" action="{{ route('logout') }}">
                    @csrf

                    <x-responsive-nav-link :href="route('logout')"
                            onclick="event.preventDefault();
                                        this.closest('form').submit();">
                        {{ __('Log Out') }}
                    </x-responsive-nav-link>
                </form>
            </div>
        </div>
    </div>
</nav>

在这里,我们添加帖子链接到桌面和移动版本导航。

测试我们的Laravel应用程序

自从安装Laravel Breeze以来还没有运行npm install,请确保首先运行它以安装所有必要的依赖项。如果您已经这样做,可以跳过此步骤。接下来,运行npm run dev开始使用Vite。

设置了前端依赖项后,您可以通过运行以下命令来运行本地开发服务器:

php artisan serve

另请阅读:

欢迎页面

您将看到以下Laravel 10欢迎页面。您还将在Navbar上看到登录登记链接,因为我们已经安装了Laravel Breeze。

Welcome page

注册自己,然后转到仪表板页。

仪表板页面

这是仪表板页面的外观。您还应该在名为帖子的Navbar上看到一个附加链接:

Dashboard page

帖子列表页面

这是/posts中的帖子列表页面的样子。它应该具有 show 编辑 delete 按钮的表格:

Post list page

显示帖子页面

这是显示帖子页面的外观:

Show post page

创建和编辑帖子页面

这是图像预览的创建邮政页面应该看起来像。这也类似于编辑帖子页面:

Create and edit post page

通知显示

我们成功创建,编辑或删除记录后应出现通知:

Notification display

结论

在本教程中,我们学会了如何使用Laravel 8和Laravel Breeze来创建一个简单的CRUD应用程序进行身份验证。我们涵盖了Laravel的基本概念,例如路由,控制器,视图和模型,以及使用Laravel Breeze的组件来处理用户身份验证。

我们还学会了如何使用尾风CSS和VITE来设计我们的应用程序并优化资产捆绑。此外,我们介绍了如何处理闪存通知并在我们的应用程序中上传。

总的来说,本教程为使用Laravel构建CRUD应用程序提供了坚实的基础,并且在获得的知识中,您可以轻松地扩展此应用程序以满足您的特定要求。

可以在fajarwz/blog-laravel10-crud-image找到此示例的存储库。