使用 Laravel 纯手工打造一个简单的电子商务网站(二) —— 用户认证及购物车功能实现

Laravel 电商网站

1、概述

上一节我们演示了如何在后台添加商品以及在前台显示,这一节我们将为商店实现用户认证购物车功能。购物车需要和用户关联并存储到数据库,以便用户下次登录还能找到自己的购物车。

因此我们首先要实现用户注册登录功能,这一实现在用户认证文档中已有详细说明,这里我们会一带而过。

注:关于本教程的完整代码已公开到GitHub:https://github.com/nonfu/laravel-store

2、用户认证

Laravel 5.2 为我们提供了认证脚手架,只需要简单运行如下命令就可以实现用户认证需要的所有代码,这里我们自己来手工打造这一功能。

首先在resources/views目录下创建一个auth子目录,在这个子目录中创建注册页面register.blade.phplogin.blade.php,首先编辑login.blade.php内容如下:

@extends('layouts.master')

@section('用户登录', 'Page Title')

@section('sidebar')
    @parent
@endsection

@section('content')
    <div class="container">
        <div id="loginbox" style="margin-top:50px;" class="col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2">
            <div class="panel panel-info" >
                <div class="panel-heading">
                    <div class="panel-title">用户登录</div>
                </div>
                <div style="padding-top:30px" class="panel-body" >
                    <div style="display:none" id="login-alert" class="alert alert-danger col-sm-12"></div>
                    <form method="POST" action="/auth/login" class="form-horizontal" role="form">
                        {!! csrf_field() !!}

                        <div style="margin-bottom: 25px" class="input-group">
                            <span class="input-group-addon"><i class="fa fa-user"></i></span>
                            <input id="email" type="text" class="form-control" name="email" value="" placeholder="邮箱">
                        </div>
                        <div style="margin-bottom: 25px" class="input-group">
                            <span class="input-group-addon"><i class="fa fa-lock"></i></span>
                            <input id="password" type="password" class="form-control" name="password" placeholder="密码">
                        </div>
                        <div class="input-group">
                            <div class="checkbox">
                                <label>
                                    <input iremember" type="checkbox" name="remember" value="1"> 记住我
                                </label>
                            </div>
                        </div>
                        <div style="margin-top:10px" class="form-group">
                            <div class="col-sm-12 controls">
                                <button type="submit" id="btn-login" href="#" class="btn btn-success">登录  </button>
                            </div>
                        </div>
                        <div class="form-group">
                            <div class="col-md-12 control">
                                <div style="border-top: 1px solid#888; padding-top:15px; font-size:85%" >
                                    <a href="/auth/register" >
                                        没有帐号?点此注册
                                    </a>
                                </div>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
@endsection

然后编辑register.blade.php内容如下:

@extends('layouts.master')

@section('用户注册', 'Page Title')

@section('sidebar')
    @parent
@endsection

@section('content')

    <div style="margin-top:50px" class="mainbox col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2">
        <div class="panel panel-info">
            <div class="panel-heading">
                <div class="panel-title">用户注册</div>
                <div style="float:right; font-size: 85%; position: relative; top:-10px"><a id="signinlink" href="/auth/login">已有帐号?前往注册>></a></div>
            </div>
            <div class="panel-body" >
                <form method="POST" action="/auth/register"  class="form-horizontal" role="form">
                        {!! csrf_field() !!}
                    <div class="form-group">
                        <label for="email" class="col-md-3 control-label">邮箱</label>
                        <div class="col-md-9">
                            <input type="text" class="form-control" name="email" placeholder="邮箱地址">
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="firstname" class="col-md-3 control-label">用户名</label>
                        <div class="col-md-9">
                            <input type="text" class="form-control" name="name" placeholder="用户名">
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="password" class="col-md-3 control-label">密码</label>
                        <div class="col-md-9">
                            <input type="password" class="form-control" name="password" placeholder="密码">
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="password" class="col-md-3 control-label">确认密码</label>
                        <div class="col-md-9">
                            <input type="password" class="form-control" name="password_confirmation" placeholder="确认密码">
                        </div>
                    </div>
                    <div class="form-group">
                        <!-- Button -->
                        <div class="col-md-offset-3 col-md-9">
                            <button type="submit" id="btn-signup" class="btn btn-success"><i class="fa fa-hand-o-right"></i> 注册</button>
                        </div>
                    </div>
                </form>
            </div>
        </div>
    </div>

@endsection

注册和登录页面都完成后我们来定义相应路由:

// 认证路由...
Route::get('auth/login', 'Auth\AuthController@getLogin');
Route::post('auth/login', 'Auth\AuthController@postLogin');
Route::get('auth/logout', 'Auth\AuthController@getLogout');

// 注册路由...
Route::get('auth/register', 'Auth\AuthController@getRegister');
Route::post('auth/register', 'Auth\AuthController@postRegister');

这样用户就可以通过相应路由实现注册、登录及退出了。注册和登录的链接已经在页面顶部导航中列出了,点击“注册”进入登录页面:

注册页面

注册成功后会自动登录并跳转到首页:

商店首页(登录后)

3、购物车实现

实现购物车之前首先要创建对应的数据表,这里我们需要两张表:cartscart_items,分别用于存放购物车信息和购物车商品明细。还是使用Artistan命令生成迁移文件:

php artisan make:migration create_table_carts
php artisan make:migration create_table_carts_item

编辑迁移文件如下:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTableCarts extends Migration
{
    public function up()
    {
        Schema::create('carts', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id');
            $table->timestamps();
        });
    }

   public function down()
    {
        Schema::drop('carts');
    }
}
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTableCartItems extends Migration

{
    public function up()
    {
        Schema::create('cart_items', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('cart_id');
            $table->integer('product_id');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::drop('cart_items');
    }
}

运行如下命令生成对应数据表:

php artisan migrate

数据表创建好了之后需要创建对应的模型类:

php artisan make:model Cart
php artisan make:model CartItem

编辑Cart模型类如下:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Cart extends Model
{
    public function user()
    {
        return $this->belongsTo('App\User');
    }

    public function cartItems()
    {
        return $this->hasMany('App\CartItem');
    }

}

编辑CartItem模型类如下:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class CartItem extends Model
{
    public function cart()
    {
        return $this->belongsTo('App\Cart');
    }

    public function product()
    {
        return $this->belongsTo('App\Product');
    }
}

具体功能实现定义在CartController中,使用Artisan命令创建这个控制器:

php artisan make:controller CartController

编辑这个控制器的内容如下:

<?php

namespace App\Http\Controllers;
use App\Cart;
use App\CartItem;
use Illuminate\Support\Facades\Auth;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

class CartController extends Controller
{

    public function __construct()
    {
        $this->middleware('auth');
    }

    public function addItem ($productId){

        $cart = Cart::where('user_id',Auth::user()->id)->first();

        if(!$cart){
            $cart =  new Cart();
            $cart->user_id=Auth::user()->id;
            $cart->save();
        }

        $cartItem  = new Cartitem();
        $cartItem->product_id=$productId;
        $cartItem->cart_id= $cart->id;
        $cartItem->save();

        return redirect('/cart');

    }

    public function showCart(){
        $cart = Cart::where('user_id',Auth::user()->id)->first();

        if(!$cart){
            $cart =  new Cart();
            $cart->user_id=Auth::user()->id;
            $cart->save();
        }

        $items = $cart->cartItems;
        $total=0;
        foreach($items as $item){
            $total+=$item->product->price;
        }

        return view('cart.view',['items'=>$items,'total'=>$total]);
    }

    public function removeItem($id){

        CartItem::destroy($id);
        return redirect('/cart');
    }

}

我们来简单看看这个控制器的代码,我们在构造函数__construct中使用了认证中间件,表示用户需要登录后才能访问这个控制器的所有动作,比如添加商品到购物车,查看购物车等。

第二个方法是addItem,这个方法首先查询与当前登录用户关联的购物车,如果获取数据为空,表示用户购物车为空,这就需要我们创建一个新的购物车实例并保存到数据库,有了Cart实例后就可以将新的CartItem关联到这个购物车,添加完成后重定向到购物车页面/cart

/cart路由会被showCart方法解析,这个方法和addProduct类似,只不过是从数据库获取与用户关联的Cart,如果没有的话会去创建一个新的实例,然后循环遍历购物车明细并计算总价,最终将数据$items$total渲染到页面视图。

最后一个方法是removeItem,该方法会删除数据库中与当前Cart关联的对应CartItem

下面我们来创建页面视图,只需要一个足矣,在resources/views/cart目录下创建view.blade.php,编辑页面内容如下:

@extends('layouts.master')

@section('购物车', 'Page Title')

@section('sidebar')
    @parent
@endsection

@section('content')

    <div class="row">
        <div class="col-sm-12 col-md-10 col-md-offset-1">
            <table class="table table-hover">
                <thead>
                <tr>
                    <th>商品</th>
                    <th></th>
                    <th class="text-center"></th>
                    <th class="text-center">小计</th>
                    <th> </th>
                </tr>
                </thead>
                <tbody>
                @foreach($items as $item)
                    <tr>
                        <td class="col-sm-8 col-md-6">
                            <div class="media">
                                <a class="thumbnail pull-left" href="#"> <img class="media-object" src="{{$item->product->imageurl}}" style="width: 100px; height: 72px;"> </a>
                                <div class="media-body">
                                    <h4 class="media-heading"><a href="#">{{$item->product->name}}</a></h4>
                                </div>
                            </div></td>
                        <td class="col-sm-1 col-md-1" style="text-align: center">
                        </td>
                        <td class="col-sm-1 col-md-1 text-center"></td>
                        <td class="col-sm-1 col-md-1 text-center"><strong>${{$item->product->price}}</strong></td>
                        <td class="col-sm-1 col-md-1">
                            <a href="/removeItem/{{$item->id}}"> <button type="button" class="btn btn-danger">
                                    <span class="fa fa-remove"></span> 移除
                                </button>
                            </a>
                        </td>
                    </tr>
                @endforeach

                <tr>
                    <td>   </td>
                    <td>   </td>
                    <td>   </td>
                    <td><h3>总价</h3></td>
                    <td class="text-right"><h3><strong>${{$total}}</strong></h3></td>
                </tr>
                <tr>
                    <td>   </td>
                    <td>   </td>
                    <td>   </td>
                    <td>
                        <a href="/"> <button type="button" class="btn btn-default">
                                <span class="fa fa-shopping-cart"></span> 继续购物
                            </button>
                        </a></td>
                    <td>
                        <button type="button" class="btn btn-success">
                            结算 <span class="fa fa-play"></span>
                        </button></td>
                </tr>
                </tbody>
            </table>
        </div>
    </div>

@endsection

之后定义路由规则如下:

Route::get('/addProduct/{productId}', 'CartController@addItem');
Route::get('/removeItem/{productId}', 'CartController@removeItem');
Route::get('/cart', 'CartController@showCart');

这样用户登录后,添加商品到购物车,在浏览器中访问购物车页面如下:

购物车页面

至此,我们的商城基本上完成了,只剩最后一步——下单支付,将购物车转化为订单并获取下载链接,关于这一实现我们将在下一节讨论。

学院君

学院君 has written 554 articles

资深PHP工程师,Laravel学院院长