Как вы сегодня, ребята?
В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что это не слишком отличается от учебника в категории продуктов.
Вы готовы?
[Шаг 1]: Добавить маршрут
Мы просто модифицируем файл routes/web.php следующим образом:
<?php
use App\Http\Livewire\Category; use GuzzleHttp\Middleware; use Illuminate\Support\Facades\Route;
/* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */
Route::get('/', function () { return view('welcome'); });
Route::middleware(['auth:sanctum', 'verified'])->group(function () { Route::get('/dashboard', function () { return view('dashboard'); })->name('dashboard');
Route::get('/categories', function () { return view('admin.categories'); })->name('categories');
Route::get('/products', function () { return view('admin.products'); })->name('products'); });
[Шаг 2]: Создайте и измените файл Livewire Controller для продукта.
Создайте файл Livewire Controller для покрытия данных продукта CRUD, создайте с помощью cmd, как и в предыдущем случае:
php artisan make:livewire Product
Затем измените файл app/http/Livewire/Product.php следующим образом:
<?php
namespace App\Http\Livewire;
use App\Models\Product as ModelsProduct; use App\Models\ProductCategory; use Livewire\Component; use Livewire\WithPagination;
class Product extends Component { use WithPagination;
public $visibleModalForm = false; public $confirmDeleteModal = false; public $name; public $price; public $description; public $tags; public $slug; public $categories_id; public $categories; public $modelId;
public function rules() { return [ 'name' => 'required', 'slug' => 'required', 'price' => 'required', 'description' => 'required', 'categories_id' => 'required' ]; }
public function updatedName($value) { $this->generateSlug($value); }
public function mount() { $this->resetPage(); }
public function read() { return ModelsProduct::orderBy('created_at', 'desc')->paginate(5); }
public function createOrUpdate() { $this->validate(); ModelsProduct::updateOrCreate(['id' => $this->modelId], $this->modelData()); $this->visibleModalForm = false; $this->resetFields(); }
public function delete() { ModelsProduct::destroy($this->modelId); $this->confirmDeleteModal = false; }
public function showUpdateModal($id) { $this->resetValidation(); $this->resetFields(); $this->modelId = $id; $this->visibleModalForm = true; $this->loadModel(); }
public function showDeleteModal($id) { $this->modelId = $id; $this->confirmDeleteModal = true; }
public function loadModel() { $data = ModelsProduct::findOrFail($this->modelId); $this->name = $data->name; $this->slug = $data->slug; $this->price = $data->price; $this->description = $data->description; $this->tags = $data->tags; $this->categories_id = $data->categories_id; $this->categories = ProductCategory::all(); }
public function modelData() { return [ 'name' => $this->name, 'slug' => $this->slug, 'price' => $this->price, 'description' => $this->description, 'tags' => $this->tags, 'categories_id' => $this->categories_id ]; }
public function resetFields() { $this->modelId = null; $this->name = null; $this->slug = null; $this->price = null; $this->description = null; $this->tags = null; $this->categories_id = null; }
private function generateSlug($value) { $dash = str_replace(' ', '-', $value); $lower = strtolower($dash); $this->slug = $lower; }
public function showModal() { $this->resetValidation(); $this->resetFields(); $this->categories = ProductCategory::all(); $this->visibleModalForm = true; }
public function render() { return view('livewire.product', [ 'data' => $this->read(), 'categories' => $this->categories ]); } }
[Шаг 3]: Добавить панель навигации по ссылкам
Чтобы добавить ссылку на панель навигации для продукта, измените файл "resources\views\navigation-menu.blade.php" следующим образом:
<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="flex-shrink-0 flex items-center"> <a href="{{ route('dashboard') }}"> <x-jet-application-mark class="block h-9 w-auto" /> </a> </div>
<!-- Navigation Links --> <div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex"> <x-jet-nav-link href="{{ route('dashboard') }}" :active="request()->routeIs('dashboard')"> {{ __('Dashboard') }} </x-jet-nav-link> <x-jet-nav-link href="{{ route('categories') }}" :active="request()->routeIs('categories')"> {{ __('Categories') }} </x-jet-nav-link> <x-jet-nav-link href="{{ route('products') }}" :active="request()->routeIs('products')"> {{ __('Products') }} </x-jet-nav-link> </div> </div>
<div class="hidden sm:flex sm:items-center sm:ml-6"> <!-- Teams Dropdown --> @if (Laravel\Jetstream\Jetstream::hasTeamFeatures()) <div class="ml-3 relative"> <x-jet-dropdown align="right" width="60"> <x-slot name="trigger"> <span class="inline-flex rounded-md"> <button type="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:bg-gray-50 hover:text-gray-700 focus:outline-none focus:bg-gray-50 active:bg-gray-50 transition"> {{ Auth::user()->currentTeam->name }}
<svg class="ml-2 -mr-0.5 h-4 w-4" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> <path fill-rule="evenodd" d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" /> </svg> </button> </span> </x-slot>
<x-slot name="content"> <div class="w-60"> <!-- Team Management --> <div class="block px-4 py-2 text-xs text-gray-400"> {{ __('Manage Team') }} </div>
<!-- Team Settings --> <x-jet-dropdown-link href="{{ route('teams.show', Auth::user()->currentTeam->id) }}"> {{ __('Team Settings') }} </x-jet-dropdown-link>
@can('create', Laravel\Jetstream\Jetstream::newTeamModel()) <x-jet-dropdown-link href="{{ route('teams.create') }}"> {{ __('Create New Team') }} </x-jet-dropdown-link> @endcan
<div class="border-t border-gray-100"></div>
<!-- Team Switcher --> <div class="block px-4 py-2 text-xs text-gray-400"> {{ __('Switch Teams') }} </div>
@foreach (Auth::user()->allTeams() as $team) <x-jet-switchable-team :team="$team" /> @endforeach </div> </x-slot> </x-jet-dropdown> </div> @endif
<!-- Settings Dropdown --> <div class="ml-3 relative"> <x-jet-dropdown align="right" width="48"> <x-slot name="trigger"> @if (Laravel\Jetstream\Jetstream::managesProfilePhotos()) <button class="flex text-sm border-2 border-transparent rounded-full focus:outline-none focus:border-gray-300 transition"> <img class="h-8 w-8 rounded-full object-cover" src="{{ Auth::user()->profile_photo_url }}" alt="{{ Auth::user()->name }}" /> </button> @else <span class="inline-flex rounded-md"> <button type="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"> {{ Auth::user()->name }}
<svg class="ml-2 -mr-0.5 h-4 w-4" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"> <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> </button> </span> @endif </x-slot>
<x-slot name="content"> <!-- Account Management --> <div class="block px-4 py-2 text-xs text-gray-400"> {{ __('Manage Account') }} </div>
<x-jet-dropdown-link href="{{ route('profile.show') }}"> {{ __('Profile') }} </x-jet-dropdown-link>
@if (Laravel\Jetstream\Jetstream::hasApiFeatures()) <x-jet-dropdown-link href="{{ route('api-tokens.index') }}"> {{ __('API Tokens') }} </x-jet-dropdown-link> @endif
<div class="border-t border-gray-100"></div>
<!-- Authentication --> <form method="POST" action="{{ route('logout') }}"> @csrf
<x-jet-dropdown-link href="{{ route('logout') }}" onclick="event.preventDefault(); this.closest('form').submit();"> {{ __('Log Out') }} </x-jet-dropdown-link> </form> </x-slot> </x-jet-dropdown> </div> </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"> <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-jet-responsive-nav-link href="{{ route('dashboard') }}" :active="request()->routeIs('dashboard')"> {{ __('Dashboard') }} </x-jet-responsive-nav-link> </div>
<!-- Responsive Settings Options --> <div class="pt-4 pb-1 border-t border-gray-200"> <div class="flex items-center px-4"> @if (Laravel\Jetstream\Jetstream::managesProfilePhotos()) <div class="flex-shrink-0 mr-3"> <img class="h-10 w-10 rounded-full object-cover" src="{{ Auth::user()->profile_photo_url }}" alt="{{ Auth::user()->name }}" /> </div> @endif
<div> <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>
<div class="mt-3 space-y-1"> <!-- Account Management --> <x-jet-responsive-nav-link href="{{ route('profile.show') }}" :active="request()->routeIs('profile.show')"> {{ __('Profile') }} </x-jet-responsive-nav-link>
@if (Laravel\Jetstream\Jetstream::hasApiFeatures()) <x-jet-responsive-nav-link href="{{ route('api-tokens.index') }}" :active="request()->routeIs('api-tokens.index')"> {{ __('API Tokens') }} </x-jet-responsive-nav-link> @endif
<!-- Authentication --> <form method="POST" action="{{ route('logout') }}"> @csrf
<x-jet-responsive-nav-link href="{{ route('logout') }}" onclick="event.preventDefault(); this.closest('form').submit();"> {{ __('Log Out') }} </x-jet-responsive-nav-link> </form>
<!-- Team Management --> @if (Laravel\Jetstream\Jetstream::hasTeamFeatures()) <div class="border-t border-gray-200"></div>
<div class="block px-4 py-2 text-xs text-gray-400"> {{ __('Manage Team') }} </div>
<!-- Team Settings --> <x-jet-responsive-nav-link href="{{ route('teams.show', Auth::user()->currentTeam->id) }}" :active="request()->routeIs('teams.show')"> {{ __('Team Settings') }} </x-jet-responsive-nav-link>
@can('create', Laravel\Jetstream\Jetstream::newTeamModel()) <x-jet-responsive-nav-link href="{{ route('teams.create') }}" :active="request()->routeIs('teams.create')"> {{ __('Create New Team') }} </x-jet-responsive-nav-link> @endcan
<div class="border-t border-gray-200"></div>
<!-- Team Switcher --> <div class="block px-4 py-2 text-xs text-gray-400"> {{ __('Switch Teams') }} </div>
@foreach (Auth::user()->allTeams() as $team) <x-jet-switchable-team :team="$team" component="jet-responsive-nav-link" /> @endforeach @endif </div> </div> </div> </nav>
[Шаг 4]: Создание и изменение вида
Создайте файл products.blade.php в каталоге "resources\views\admin" и заполните этот код:
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Products') }} </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-xl sm:rounded-lg"> @livewire('product') </div> </div> </div> </x-app-layout>
Затем измените файл «C:\xampp\htdocs\myshop_backend\resources\views\livewire\product.blade.php» следующим образом:
<div class="p-6"> <div class="flex items-center px-4 py-3 text-right sm:px-6"> <x-jet-button wire:click="showModal"> {{ __('Add Product') }} </x-jet-button> </div>
<!-- Datatables --> <div class="flex flex-col"> <div class="my-2 overflow-x-auto sm:mx-6 lg:mx-8"> <div class="py-2 align-middle inline-block w-full sm:px-6 lg:px-8"> <div class="shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"> <table class="w-full divide-y divide-gray-200"> <thead> <tr> <th class="px-6 py-3 bg-gray-100 text-center text-sm leading-4 font-medium text-gray-500 upercase tracking-wider">Name</th> <th class="px-6 py-3 bg-gray-100 text-center text-sm leading-4 font-medium text-gray-500 upercase tracking-wider">Slug</th> <th class="px-6 py-3 bg-gray-100 text-center text-sm leading-4 font-medium text-gray-500 upercase tracking-wider">Category</th> <th class="px-6 py-3 bg-gray-100 text-center text-sm leading-4 font-medium text-gray-500 upercase tracking-wider">Price</th> <th class="px-6 py-3 bg-gray-100 text-center text-sm leading-4 font-medium text-gray-500 upercase tracking-wider">Description</th> <th class="px-6 py-3 bg-gray-100 text-center text-sm leading-4 font-medium text-gray-500 upercase tracking-wider">Tags</th> <th class="px-6 py-3 bg-gray-100 text-center text-sm leading-4 font-medium text-gray-500 upercase tracking-wider">Actions</th> </tr> </thead> <tbody class="bg-white divide-y divide-gray-200"> @if ($data->count()) @foreach ($data as $item) <tr> <td class="px-6 py-4 text-center text-base whitespace-no-wrap">{{ $item->name }}</td> <td class="px-6 py-4 text-center text-base whitespace-no-wrap">{{ $item->slug }}</td> <td class="px-6 py-4 text-center text-base whitespace-no-wrap">{{ $item->category->name }}</td> <td class="px-6 py-4 text-center text-base whitespace-no-wrap">{{ $item->price }}</td> <td class="px-6 py-4 text-center text-base whitespace-no-wrap">{{ $item->description }}</td> <td class="px-6 py-4 text-center text-base whitespace-no-wrap">{{ $item->tags }}</td> <td class="px-6 py-4 text-center text-base"> <x-jet-button wire:click="showUpdateModal({{ $item->id }})"> {{ __('Update') }} </x-jet-button>
<x-jet-danger-button class="ml-2" wire:click="showDeleteModal({{ $item->id }})" wire:loading.attr="disabled"> {{ __('Delete') }} </x-jet-danger-button> </td> </tr> @endforeach @else <tr> <td class="px-6 py-4 text-center text-base whitespace-no-wrap" colspan="4">No Result Found</td> </tr> @endif
</tbody> </table> </div> </div> </div> </div>
<br />
{{ $data->links() }}
<!-- Show Modal --> <x-jet-dialog-modal wire:model="visibleModalForm"> <x-slot name="title"> {{ __('Form Add Product') }} </x-slot>
<x-slot name="content"> <div class="mt-4"> <x-jet-label for="name" value="{{ __('Product Name') }}" /> <x-jet-input type="text" class="mt-1 block w-3/4" placeholder="{{ __('Product Name') }}" wire:model="name" /> <x-jet-input-error for="name" class="mt-2" /> </div> <div class="mt-4"> <x-jet-label for="title" value="{{ __('Slug') }}" /> <div class="mt-1 flex w-3/4"> <span class="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 text-sm"> https://localhost:8000/ </span> <input type="text" wire:model="slug" class="mt-1 block w-3/4 form-control border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-r-md shadow-sm" placeholder="url-slug" readonly /> </div> <x-jet-input-error for="slug" class="mt-2" /> </div> <div class="mt-4"> <x-jet-label for="price" value="{{ __('Price') }}" /> <x-jet-input type="text" class="mt-1 block w-3/4" placeholder="{{ __('Price') }}" wire:model.defer="price" /> <x-jet-input-error for="price" class="mt-2" /> </div> <div class="mt-4"> <x-jet-label for="categories_id" value="{{ __('Category') }}" /> <select wire:model.defer="categories_id" class="js-example-basic-single mt-1 block w-3/4 form-control border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"> <option selected>-- Choice Categories --</option> @if($categories) @foreach($categories as $cat) @if($cat->id == $categories_id) <option value="{{ $cat->id }}" selected>{{ $cat->name }}</option> @else <option value="{{ $cat->id }}">{{ $cat->name }}</option> @endif @endforeach @endif </select> <x-jet-input-error for="categories_id" class="mt-2" /> </div> <div class="mt-4"> <x-jet-label for="description" value="{{ __('Description') }}" /> <textarea wire:model.defer="description" placeholder="Description" class="mt-1 block w-3/4 form-control border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"> </textarea> <x-jet-input-error for="description" class="mt-2" /> </div> </x-slot>
<x-slot name="footer"> <x-jet-secondary-button wire:click="$toggle('visibleModalForm')"> {{ __('Cancel') }} </x-jet-secondary-button>
@if ($modelId) <x-jet-danger-button class="ml-2" wire:click="createOrUpdate" wire:loading.attr="disabled"> {{ __('Update Product') }} </x-jet-danger-button> @else <x-jet-danger-button class="ml-2" wire:click="createOrUpdate" wire:loading.attr="disabled"> {{ __('Add Product') }} </x-jet-danger-button> @endif
</x-slot> </x-jet-dialog-modal>
<!-- Show Delete Modal --> <x-jet-dialog-modal wire:model="confirmDeleteModal"> <x-slot name="title"> {{ __('Delete Product') }} </x-slot>
<x-slot name="content"> {{ __('Are you sure you want to delete this Product data ? Once your data is deleted, all of its resources and data will be permanently deleted.') }} </x-slot>
<x-slot name="footer"> <x-jet-secondary-button wire:click="$toggle('confirmDeleteModal')"> {{ __('Cancel') }} </x-jet-secondary-button>
<x-jet-danger-button class="ml-2" wire:click="delete" wire:loading.attr="disabled"> {{ __('Delete') }} </x-jet-danger-button>
</x-slot> </x-jet-dialog-modal> </div>
Я думаю, что все готово, мы просто проверяем в браузере, что он работает успешно?
[Шаг 5]: Запустите и проверьте проект
Запустим проект в браузере.
Попробуйте создать продукт:
Слаг поля будет заполнен автоматически. а затем нажмите Добавить продукт
Ага.. Успешно. Затем попробуйте обновить данные
Измените цену и обновите.
Хорошо, он создан правильно. Далее, как насчет удаления? Давайте попробуем проверить.
И ты…
Большой !!! Успешно !!!
В следующей части я попытаюсь создать API, который будет использоваться внешним интерфейсом, таким как React js и Flutter для мобильных устройств.