Laravel项目基于Bootstrap模态框实现购物车选品功能的问题咨询
实现方案
我们采用Session存储临时购物车数据 + jQuery异步请求动态刷新的方案解决所有问题,步骤如下:
步骤1:新增路由
在routes/web.php中新增购物车临时操作路由:
use App\Http\Controllers\CartController; // 购物车商品增删临时接口 Route::post('/cart/add-item', [CartController::class, 'addItem'])->name('cart.addItem'); Route::post('/cart/remove-item', [CartController::class, 'removeItem'])->name('cart.removeItem'); // 原有资源路由保留 Route::resource('carts', CartController::class);
步骤2:补充CartController逻辑
<?php namespace App\Http\Controllers; use App\Models\Product; use App\Models\Cart; use App\Models\CartProd; use Illuminate\Http\Request; use Illuminate\Support\Facades\Session; use Illuminate\Support\Facades\DB; class CartController extends Controller { public function create(Request $request) { $product_list = Product::all(); $empty_cart = new Cart(); // 首次进入清空旧的临时购物车数据 Session::forget('selected_products'); $selected_products = Session::get('selected_products', []); return view('models.carts.create', compact('product_list','empty_cart','selected_products')); } // 添加商品到临时购物车 public function addItem(Request $request) { $validated = $request->validate([ 'product_id' => 'required|exists:products,id', 'amount' => 'required|integer|min:1', 'discount' => 'required|numeric|min:0|max:100' ]); $product = Product::findOrFail($validated['product_id']); // 校验库存 if ($validated['amount'] > $product->amount) { return response()->json(['status' => 0, 'msg' => '库存不足']); } // 计算CartProd属性 $unit_price = $product->price; $total_cost = $unit_price * $validated['amount'] * (1 - $validated['discount'] / 100); // 组装临时CartProd对象 $cartItem = new CartProd(); $cartItem->product_id = $product->id; $cartItem->product = $product; $cartItem->amount = $validated['amount']; $cartItem->discount = $validated['discount']; $cartItem->unit_price = $unit_price; $cartItem->total_cost = $total_cost; // 存入Session $selected = Session::get('selected_products', []); // 避免重复添加同商品,如需叠加数量可修改此处逻辑 $exists = collect($selected)->where('product_id', $product->id)->first(); if ($exists) { return response()->json(['status' => 0, 'msg' => '该商品已在购物车中']); } $selected[] = $cartItem; Session::put('selected_products', $selected); // 返回渲染后的列表HTML $html = view('models.carts.partials.selected_list', compact('selected'))->render(); return response()->json(['status' => 1, 'html' => $html]); } // 删除临时购物车商品 public function removeItem(Request $request) { $validated = $request->validate([ 'product_id' => 'required|integer' ]); $selected = Session::get('selected_products', []); // 过滤要删除的商品 $selected = collect($selected)->filter(function($item) use ($validated) { return $item->product_id != $validated['product_id']; })->values()->toArray(); Session::put('selected_products', $selected); $html = view('models.carts.partials.selected_list', compact('selected'))->render(); return response()->json(['status' => 1, 'html' => $html]); } // 提交购物车存储逻辑 public function store(Request $request) { $selected = Session::get('selected_products', []); if (empty($selected)) { return back()->withErrors('购物车不能为空'); } // 开启事务保证数据一致性 DB::beginTransaction(); try { // 计算购物车总金额 $total_cost = collect($selected)->sum('total_cost'); // 创建Cart主表 $cart = Cart::create([ 'cost' => $total_cost // 其他字段如user_id按需补充 ]); // 批量创建CartProd关联数据 $cart_prod_data = collect($selected)->map(function($item) use ($cart) { return [ 'cart_id' => $cart->id, 'product_id' => $item->product_id, 'unit_price' => $item->unit_price, 'amount' => $item->amount, 'discount' => $item->discount, 'total_cost' => $item->total_cost ]; })->toArray(); CartProd::insert($cart_prod_data); // 扣商品库存(可选,按业务需求调整) foreach ($selected as $item) { Product::find($item->product_id)->decrement('amount', $item->amount); } // 清空临时购物车 Session::forget('selected_products'); DB::commit(); return redirect()->route('carts.index')->with('success', '购物车创建成功'); } catch (\Exception $e) { DB::rollBack(); return back()->withErrors('操作失败:'.$e->getMessage()); } } }
步骤3:抽离选中商品列表公共模板
新建resources/views/models/carts/partials/selected_list.blade.php:
@forelse($selected as $index => $prodCart) <tr> <td>{{ $index + 1 }}</td> <td>{{ $prodCart->product->name }}</td> <td>{{ $prodCart->unit_price }}</td> <td>{{ $prodCart->amount }}</td> <td>{{ $prodCart->discount }}%</td> <td>{{ $prodCart->total_cost }}</td> <td> <a class="btn btn-danger btn-circle btn-sm delete-btn" data-product-id="{{ $prodCart->product_id }}"> <i class="fa fa-trash"></i> </a> </td> </tr> @empty <tr><td colspan="7">Empty Cart</td></tr> @endforelse
步骤4:修改原有视图文件
4.1 调整create.blade.php
@extends('adminlte::page') @section('content') <div class="container"><div class="row justify-content-center"><div class="col-md-8"><div class="card"> <div class="card-header"> <h3 class="card-title"> New Cart </h3> <div class="card-tools"> <a class="btn btn-success btn-tool" href="#" data-toggle="modal" data-target="#add"> <i class="fas fa-plus"></i> Add Product </a> </div> </div> <div class="card-body"> <table class="table table-striped projects table-sm"> <thead><tr><th>#</th><th>Name</th><th>Unit Cost</th><th>Amount</th><th>Discount</th><th>Final Cost</th><th></th></tr></thead> <tbody id="selected-list"> @include('models.carts.partials.selected_list', ['selected' => $selected_products]) </tbody> </table> </div> <div class="card-footer" id="cart-footer"> @if(!empty($selected_products)) <form action="{{ route('carts.store') }}" method="POST" style="display:inline;"> @csrf <button type="submit" class="btn btn-success"> Buy Cart </button> </form> @endif </div> </div></div></div></div> @include('models.carts.add') @endsection @section('scripts') <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script> <script> // 添加商品事件 $('.add-btn').click(function() { const productId = $(this).data('product-id'); const amount = $(`input[name="amount_${productId}"]`).val(); const discount = $(`input[name="discount_${productId}"]`).val(); $.post("{{ route('cart.addItem') }}", { _token: "{{ csrf_token() }}", product_id: productId, amount: amount, discount: discount }, function(res) { if (res.status === 1) { $('#selected-list').html(res.html); // 更新底部购买按钮 if ($('#selected-list').text().indexOf('Empty Cart') === -1) { $('#cart-footer').html(` <form action="{{ route('carts.store') }}" method="POST" style="display:inline;"> @csrf <button type="submit" class="btn btn-success"> Buy Cart </button> </form> `); } $('#add').modal('hide'); alert('添加成功'); } else { alert(res.msg); } }) }) // 删除商品事件 $(document).on('click', '.delete-btn', function() { const productId = $(this).data('product-id'); if (confirm('确定删除该商品?')) { $.post("{{ route('cart.removeItem') }}", { _token: "{{ csrf_token() }}", product_id: productId }, function(res) { $('#selected-list').html(res.html); // 更新底部购买按钮 if ($('#selected-list').text().indexOf('Empty Cart') !== -1) { $('#cart-footer').html(''); } alert('删除成功'); }) } }) </script> @stop
4.2 调整add.blade.php(解决input id重复问题)
<!--Modal add --> <div class="modal fade" id="add" tabindex="-1" role="dialog" aria-hidden="true"><div class="modal-dialog modal-lg"><div class="modal-content"> <div class="modal-header bg-dark"> <h5 class="modal-title" id="exampleModalLabel">Add Product to Cart</h5> <span class="badge badge-danger" class="close" data-dismiss="modal" aria-label="Close"> <i class="fas fa-times fa-lg" style="color:#fff"></i> </span> </div> <div class="modal-body"> <table class="table table-striped projects table-sm"> <thead><tr><th>#</th><th>Name</th><th>Amount</th><th>Price</th><th>Added</th><th>Discount</th><th>Amount</th><th>Add</th></tr></thead> <tbody> @forelse($product_list as $product) <tr> <td>{{ $loop->iteration }}</td> <td>{{ $product->name }}</td> <td>{{ $product->amount }}</td> <td>{{ $product->price }}</td> <td>{{ $product->created_at }}</td> <td><input type="number" name="discount_{{$product->id}}" min="0" max="100" size="3" value="0"></td> <td><input type="number" name="amount_{{$product->id}}" min="1" max="{{$product->amount}}" size="3" value="1"></td> <td> <a class="btn btn-info btn-circle btn-sm add-btn" data-product-id="{{$product->id}}"> <i class="fa fa-plus"></i> </a> </td> </tr> @empty <tr> <td>No Products Available.</td> </tr> @endforelse </tbody> </table> </div></div></div></div>
问题对应解决说明
- 模态框添加商品:通过按钮绑定点击事件,将商品ID、数量、折扣异步提交到
addItem方法,生成临时CartProd对象存入Session - 删除误加商品:删除按钮绑定点击事件,提交商品ID到
removeItem方法,过滤Session中对应商品 - 自动更新列表:每次增删操作成功后,后端返回渲染好的列表HTML,前端直接替换DOM内容
- 提交购物车:购买按钮绑定POST表单提交到
store方法,通过事务生成Cart、CartProd记录,扣减库存后清空Session
内容的提问来源于stack exchange,提问作者FrankV427
相关产品推荐
相关产品推荐

