返回

Livewire 动态购物车:无刷新页面更新教程

php

使用 Livewire 实现动态购物车功能

项目中实现无需页面刷新的动态购物车体验是提升用户体验的关键。Livewire 提供了一种便捷的方法,能够实现页面的局部更新。当尝试配置 Livewire 失败后,通常的问题可能出现在以下几个方面。

问题分析

页面重新加载的问题通常源于以下几点:

  1. 表单提交行为: HTML 表单默认行为是提交后刷新页面。 即使使用了 Livewire,若未阻止表单默认提交或使用 Livewire 提供的方式,仍会导致页面刷新。

  2. Livewire 组件状态管理: Livewire 组件的重新渲染和数据同步,可能会因为状态管理不当造成意外的行为,特别是当涉及到父组件和子组件的嵌套使用时。

  3. JavaScript 代码冲突: 前端 JavaScript 代码(如打开模态窗口的 JS 代码)和 Livewire 交互时可能产生冲突,需要谨慎处理交互细节。

  4. Livewire 绑定: Livewire 组件在视图中未正确地使用数据绑定, 可能造成页面数据不更新, 或在每次动作触发时强制页面重载。

解决方案

以下是配置 Livewire 的详细步骤和代码示例,并针对常见问题给出解释。

步骤一: 将表单操作移入 Livewire 组件

避免直接使用 HTML 表单提交。使用 Livewire 的方法调用替代传统的表单提交。这通过 Livewire 框架自动实现 AJAX 请求,避免页面刷新。

// App\Livewire\ProductList.php
namespace App\Livewire;

use Livewire\Component;
use App\Models\Cigarette;
use App\Models\Liquid;
use Gloudemans\Shoppingcart\Facades\Cart;

class ProductList extends Component
{
    public $cigarettes;
    public $liquids;
    public $cart;
    public $total;

    public function mount()
    {
        $this->loadProducts();
    }

    public function addToCart($productId, $type, $quantity = 1)
    {
          // 找到相应的产品
        if($type == "cigarette") {
          $product = Cigarette::findOrFail($productId);
        }else {
            $product = Liquid::findOrFail($productId);
        }


        Cart::add($product, $quantity, 0);
        $this->loadProducts();
    }

      public function removeFromCart($rowId)
      {
            Cart::remove($rowId);
          $this->loadProducts();

    }


    private function loadProducts() {
          $this->cigarettes = Cigarette::all();
          $this->liquids = Liquid::all();
          $this->cart = Cart::content();
          $this->total = Cart::priceTotal();

    }


    public function render()
    {
        return view('livewire.product-list');
    }
}

    {{-- resources/views/livewire/product-list.blade.php --}}
<div class="grid">
  @foreach($cigarettes as $cigarette)
    <div class="grid__item">
        <article class="product {{ $cigarette->type }}">
             <img class="product__image" src="{{ asset('./storage/' . $cigarette->image) }}" alt="{{ $cigarette->name }}" />

            <div class="product__description">
            <h2 class="product__name">{{ $cigarette->name }}</h2>

            <div class="product__info">&nbsp;Moc - {{ $cigarette->strength }}%
                <br>&nbsp;{{ $cigarette->puffs }} zaciągnięć
                <br>&nbsp;Smak: {{ $cigarette->flavor }}
            </div>
                 <p class="product__price">Cena: {{ $cigarette->price }}</p>

                {{-- Cart button --}}

                 {{-- Quantity control --}}
                     <div class="quantity-control">
                     <button type="button" class="quantity-button decrease"  wire:click.prevent="">-</button>

                     <input type="number" name="quantity" class="quantity-input" value="1" min="1" readonly />

                      <button type="button" class="quantity-button increase"  wire:click.prevent="">+</button>
                   </div>

                  <button type="submit"
                      class="button product__button"  wire:click.prevent="addToCart('{{ $cigarette->id }}','cigarette')">

                       <svg class="button__icon" width="20" height="20">
                             <use href="./storage/images/footer-icons/symbol-defs.svg#icon-shopping-cart"></use>
                           </svg>
                        </button>
                   </button>


           <!--Delete from cart-->
           @foreach($cart as $item)

                 @if($item->id == $cigarette->id)
                 <button class="#" wire:click.prevent="removeFromCart('{{ $item->rowId }}')"> Delete </button>
                    @endif
                 @endforeach

             </div>

           </article>
   </div>
      @endforeach

         @foreach($liquids as $liquid)
                <div class="grid__item">
                    <article class="product {{ $liquid->type }}">
                          <img class="product__image" src="{{ asset('./storage/' . $liquid->image) }}" alt="{{ $liquid->name }}" />
                      <div class="product__description">
                        <h2 class="product__name">{{ $liquid->name }}</h2>
                           <div class="product__info">&nbsp;Moc - {{ $liquid->strength }}%
                            <br>&nbsp;{{ $liquid->volume }} ml
                                <br>&nbsp;Smak: {{ $liquid->flavor }}
                         </div>
                           <p class="product__price">Cena: {{ $liquid->price }}</p>
                           <div class="quantity-control">
                          <button type="button" class="quantity-button decrease"  wire:click.prevent="">-</button>

                            <input type="number" name="quantity" class="quantity-input" value="1" min="1" readonly/>
                           <button type="button" class="quantity-button increase"  wire:click.prevent="">+</button>
                        </div>
                     <button type="submit"
                       class="button product__button" aria-label="Dodaj do koszyka"  wire:click.prevent="addToCart('{{ $liquid->id }}','liquid')">

                         <svg class="button__icon" width="20" height="20">
                         <use href="./storage/images/footer-icons/symbol-defs.svg#icon-shopping-cart"></use>
                       </svg>
                    </button>
                </button>
                @foreach($cart as $item)
                 @if($item->id == $liquid->id)
                    <button class="#"  wire:click.prevent="removeFromCart('{{ $item->rowId }}')"> Delete </button>
                       @endif
                     @endforeach
                 </div>

            </article>
           </div>
     @endforeach

</div>
  • 操作步骤:
    1. 使用命令 php artisan make:livewire ProductList 生成一个名为 ProductList 的 Livewire 组件.
    2. 将上述代码替换到对应的组件类和模板视图中。
    3. index.blade.php 中, 用 @livewire('product-list') 替换原有循环代码部分.
    4. Livewire 代码里wire:click.prevent用于阻止表单默认行为。使用Livewire的组件渲染方式 wire:click 调用 addToCart 函数。同时传入商品ID productId 以及 商品类型 type作为参数。
    5. 当商品加入购物车或者移除时,触发 loadProducts() 函数去刷新商品列表,同步最新状态,最后呈现给用户。
    6. 修改删除商品按钮点击事件使用removeFromCart() 方法, 同样通过wire:click.prevent 来防止刷新页面. rowId 为 Cart 的 id.
    7. 模板代码里删除 form标签,以及csrf的设置,@csrf 会造成错误,因为提交方式已经是使用了 Livewire 组件内部的方式.
    8. 注意购物车中where() 条件要改成通过 id 来筛选.

步骤二:处理 JavaScript 代码冲突

如果你的模态窗口使用了 JavaScript 代码,需要注意以下几点:

  • Livewire 钩子函数 : 可以使用 Livewire 的钩子函数,例如 component.initializedcomponent.updated 来控制模态窗口的行为。在 Livewire 组件初始化或更新时,更新 JavaScript 逻辑。

  • 事件监听 : 使用 Livewire 事件监听机制 ($this->dispatch('eventName',data)) 和前端 JS 代码相结合, 当需要更新前端时通过事件来进行通信。

// resources/js/app.js
Livewire.on('cartUpdated', ()=>{
   // 处理模态窗口,重新计算内容, 例如更新商品总数或者直接展示模态框

  console.log("购物车更新!");
})
// App\Livewire\ProductList.php
public function addToCart($productId, $type, $quantity = 1)
{

    // 其他逻辑 ...
       $this->dispatch('cartUpdated');
}

public function removeFromCart($rowId)
{
    // 其他逻辑 ...
      $this->dispatch('cartUpdated');
}

  • 操作步骤:
    1. 在前端 app.js 中注册 Livewire 事件监听。
    2. 在Livewire的addToCart, removeFromCart 添加 $this->dispatch 来触发 cartUpdated 事件, 传递需要的消息或信息。
    3. 针对页面和模态窗进行相关 JS 操作,控制元素的行为。

安全建议

  • 数据验证: 始终在服务器端对接收的数据进行验证,确保数据符合预期格式,防止恶意数据注入。
  • 输入过滤: 过滤用户输入,确保不会存储和显示恶意的 HTML 代码或脚本,降低 XSS 攻击风险。

通过上述配置,你应该可以成功解决页面重新加载的问题,并使用 Livewire 实现一个动态更新的购物车。 请记住检查控制台日志中任何可能发生的错误信息,以及时排查问题。