Xây dựng website bán hàng bằng Laravel - Tổ chức và Phân loại Sản phẩm

TQH 2023-08-30 17:40:19

Một sản phẩm có thể nằm trong một hoặc nhiều danh mục khác nhau. Để phân phối sản phẩm vào các danh mục chúng ta cần thêm một tính năng trong phần quản lý sản phẩm, hãy cùng tìm hiểu cách thực hiện trong bài viết này nhé!

Quay trở lại với bài viết Xây dựng Danh mục cho Sản phẩm chúng ta đã có thể tạo danh sách các danh mục cần có trong website thương mại điện tử của mình. Việc tiếp theo cần làm là thêm tính năng để tổ chức và phân loại sản phẩm theo danh mục hoặc theo thương hiệu. Tuy nhiên hiện tại chúng ta mới chỉ có phần danh mục nên trước hết hãy tập trung vào phân loại theo danh mục trước nhé!

Nếu như bạn để ý trong phần tạo migration tại bài viết Xây dựng thông tin sản phẩm chúng ta không có cột dành riêng cho việc xử lý quan hệ giữa sản phẩm và danh mục. Lý do là vì một sản phẩm có thể nằm trong một hoặc nhiều danh mục và ngược lại một danh mục sẽ có nhiều sản phẩm khác nhau (quan hệ n-n), vì vậy chúng ta sẽ cần tạo một bảng mới trên cơ sở dữ liệu để quản lý mối quan hệ này.

Bắt đầu tạo migration với make:migration

php artisan make:migration create_category_product_table

Chúng ta sẽ không cần sử dụng đến model để quản lý bảng này vì nó chỉ đóng vai trò là trung gian lưu trữ các khóa ngoại đến hai bảng categories và products mà thôi. Nội dung của nó sẽ như sau:

public function up()
{
    Schema::create('category_product', function (Blueprint $table) {
        $table->foreignIdFor(\App\Models\Category::class)->constrained()->cascadeOnDelete();
        $table->foreignIdFor(\App\Models\Product::class)->constrained()->cascadeOnDelete();
    });
}

Và chạy migrate để tiến hành tạo bảng

php artisan migrate

Tiếp đến chúng ta tiến hành khai báo về mối quan hệ giữa sản phẩm và danh mục như sau:

tại App/Models/Category.php:

public function products(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
    return $this->belongsToMany(Product::class);
}

và App/Models/Product.php:

public function categories(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
    return $this->belongsToMany(Category::class);
}

Ok, vậy là xong phần cơ sở dữ liệu và thiết lập quan hệ. Tiếp theo chúng ta sẽ tạo livewire component để thực hiện thao tác gán sản phẩm vào các danh mục đã tạo. Bắt đầu bằng lệnh livewire:make:

php artisan livewire:make ProductOrganization

Tiến hành khai báo biến $product như thường lệ, và tại phương thức render lúc này chúng ta thực hiện nhúng danh sách toàn bộ các danh mục đã tạo với biến $categories để tạo một select box trên phần quản lý như sau:

class ProductOrganizationForm extends Component
{
    public Product $product;
    public $selectedCategories = [];

    public function mount()
    {
        $this->selectedCategories = $this->product->categories->pluck('id')->toArray();
    }

    public function save()
    {
        $this->product->categories()->sync($this->selectedCategories);
        $this->emitSelf('saved');
    }

    public function render()
    {
        return view('livewire.admin.product-organization-form', ['categories' => Category::all()]);
    }
}

Ngoài ra thì trên đây tôi cũng đồng thời khai báo một biến là $selectedCategories và truyền vào nó danh sách ID của các danh mục đã gán cho sản phẩm này, và một phương thức save để lưu lại các danh mục đã chọn.

Tại phần view của component này chúng ta sẽ tạo một html select với thuộc tính là multiple (chọn nhiều) như sau:

<div>
    <form wire:submit.prevent="save">
        <x-card class="-mx-4 mt-5 sm:-mx-0">
            <x-slot name="header">
                <div class="ml-4 mt-2">
                    <h3 class="text-lg leading-6 font-medium text-gray-900">Organization</h3>
                </div>
            </x-slot>
            <x-slot name="content">
                <div>
                    <x-label
                        for="categories"
                        value="{{ __('Categories') }}"
                    />
                    <x-select
                        wire:model.defer="selectedCategories"
                        class="mt-1 block w-full"
                        multiple
                    >
                        @foreach($categories as $category)
                            <option
                                value="{{ $category->id }}"
                                @selected(in_array($category->id, $product->categories->pluck('id')->toArray()))
                            >
                                {{ $category->name }}
                            </option>
                        @endforeach
                    </x-select>
                </div>
            </x-slot>
            <x-slot name="footer">
                <div class="flex items-center justify-end">
                    <x-action-message on="saved" class="mr-2" />
                    <x-primary-button wire:loading.attr="disabled">
                        {{ __('Save') }}
                    </x-primary-button>
                </div>
            </x-slot>
        </x-card>
    </form>
</div>

Để giải thích thêm một chút trong thẻ option, tại đây tôi sử dụng @checked blade directive và tiến hành kiểm tra nếu ID của danh mục có nằm trong danh sách các danh mục của sản phẩm đó hay không. Directive này sẽ tự động gán thuộc tính selected vào thẻ option của chúng ta nếu điều kiện kiểm tra cho ra kết quả đúng.

Và tiến hành nhúng view này vào view của ProductManager component

<div class="col-span-3 xl:col-span-1">
    <livewire:admin.product-organization-form :product="$product" />
</div>