ASP.NET MVC實現橫向展示購物車

通常,我們看到的購物車是這樣的:

3

雖然這種購物車顯示方式被廣泛運用,但我個人覺得不夠直觀。如果換成這樣呢?

本篇的源碼放在瞭:https://github.com/darrenji/ShoppingCartInMVC

以上購物車頁能實現的效果包括:
1、購物車明細:顯示訂購數量、總金額,清空購物車。
2、購物車內產品:數量可調整,對應的小計和總計動態變化。點擊移除按鈕移除該產品。
3、繼續購物按鈕:點擊左下角的繼續購物按鈕,回到先前頁。
4、使用瞭Bootstrap, 頁面元素自適應,頁面寬度調小時,頁面佈局動態變化。
5、每行放置4個產品,且允許高度不一致,第5個產品另起一行,且不會float到上一行的空白區域,如下圖。

首先,有關產品的類。

    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string ImageUrl { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
    }

產品選購頁如圖:

以上,產品選購頁是一個有關Product集合的強類型視圖頁,其對應的Model為:

    public class ProductsListVm
    {
        public ProductsListVm()
        {
            this.Products = new List<Product>();
        }
        public IEnumerable<Product> Products { get; set; }
    }

想像一下,我們在超市購物,在購物車內放著不同的商品對應不同的數量,在這裡,可以把商品和數量抽象成一個類:

    public class CartLine
    {
        public Product Product { get; set; }
        public int Quantity { get; set; }
    }

而購物車類實際上就是維護著這個CartLine集合,需要提供添加、移除、計算購物車總價、清空購物車等方法,並提供一個獲取到CartLine集合的屬性,另外,針對點擊購物車頁上的增量和減量按鈕,也要提供相應的方法。

    public class Cart
    {
        private List<CartLine> lineCollection = new List<CartLine>();
        //添加
        public void AddItem(Product product, int quantity)
        {
            CartLine line = lineCollection.Where(p => p.Product.Id == product.Id).FirstOrDefault();
            if (line == null)
            {
                lineCollection.Add(new CartLine(){Product = product, Quantity = quantity});
            }
            else
            {
                line.Quantity += quantity;
            }
        }
        //點擊數量+號或點擊數量-號或自己輸入一個值
        public void IncreaseOrDecreaseOne(Product product, int quantity)
        {
            CartLine line = lineCollection.Where(p => p.Product.Id == product.Id).FirstOrDefault();
            if (line != null)
            {
                line.Quantity = quantity;
            }
        }
        //移除
        public void RemoveLine(Product product)
        {
            lineCollection.RemoveAll(p => p.Product.Id == product.Id);
        }
        //計算總價
        public decimal ComputeTotalPrice()
        {
            return lineCollection.Sum(p => p.Product.Price*p.Quantity);
        }
        //清空
        public void Clear()
        {
            lineCollection.Clear();
        }
        //獲取
        public IEnumerable<CartLine> Lines
        {
            get { return lineCollection; }
        }
    }

購物車頁自然就是針對Cart類的一個強類型視圖頁,嗯,等等,購物車頁還需要記錄下上一個頁面的url,於是,考慮到把Cart類和記錄上一個頁面url這2個因素,針對購物車頁,給出這樣的一個Model:

    public class CartIndexVm
    {
        public Cart Cart { get; set; }
        public string ReturnUrl { get; set; }
    }

在HomeController中,需要用到購物車的實例,可以這樣寫:

private Cart GetCart()
{
    Cart cart = (Cart)Session["Cart"];
    if (cart == null)
    {
        cart = new Cart();
        Session["Cart"] = cart;
    }
    return cart;
}

Cart實例保存到Session中,並從Session中獲取。當然,也可以放到ASP.NET MVC綁定機制中,需要做的就是實現IModelBinder接口。

    public class CartModelBinder : IModelBinder
    {
        private const string sessionKey = "Cart";
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            Cart cart = (Cart)controllerContext.HttpContext.Session[sessionKey];
            if (cart == null)
            {
                cart = new Cart();
                controllerContext.HttpContext.Session[sessionKey] = cart;
            }
            return cart;
        }
    }

自定義的ModelBinder需要在全局中註冊。

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            ......
            ModelBinders.Binders.Add(typeof(Cart), new CartModelBinder());
        }
    }

在Home控制器中,首先提供瞭一個返回Product集合的方法。

        private List<Product> GetAllProducts()
        {
            return new List<Product>()
            {
                new Product(){Id = 1, Description = "產品描述產品描述產品描述產品描述產品描述產品描述產品描述",ImageUrl = "/images/1.jpg",Name = "產品1",Price = 85M},
                new Product(){Id = 2, Description = "產品描述產品描述產品描述產品描述產品描述產品描述產品描述",ImageUrl = "/images/2.jpg",Name = "產品2",Price = 95M},
                new Product(){Id = 3, Description = "產品描述產品描述產品描述",ImageUrl = "/images/2.jpg",Name = "產品3",Price = 55M},
                new Product(){Id = 4, Description = "產品描述產品描述產品描述產品描述產品描述產品描述產品描述",ImageUrl = "/images/1.jpg",Name = "產品4",Price = 65M},
                new Product(){Id = 5, Description = "產品描述產品描述產品描述產品描述產品描述產品描述產品描述",ImageUrl = "/images/2.jpg",Name = "產品5",Price = 75M}
            };
        }

在HomeController中,有關產品選購頁的如下:

        //產品選購頁
        public ActionResult Index()
        {
            ProductsListVm productsListVm = new ProductsListVm();
            productsListVm.Products = GetAllProducts();
            return View(productsListVm);
        }

Homme/Index.cshtml是一個ProductsListVm的強類型視圖頁。

@model MvcApplication1.Models.ProductsListVm
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<style type="text/css">
    .item {
        border-bottom: solid 1px gray;
    }
</style>
<div class="container">
    <div class="row">
        @foreach (var item in Model.Products)
        {
            Html.RenderPartial("ProductSummary", item);
        }
    </div>
</div>

其中,遍歷Product集合的時候,又去加載Views/Shared/ProductSummary.cshtml這個強類型部分視圖。

@model MvcApplication1.Models.Product
<div class="item">
    <h3>@Model.Name</h3>
    <p><img src="@Model.ImageUrl" style="width: 100px;height: 100px;"/></p>
    <p>@Model.Description</p>
    <h4>@Model.Price.ToString("c")</h4>
    @using (Html.BeginForm("AddToCart", "Home"))
    {
        @Html.HiddenFor(p => p.Id)
        @Html.Hidden("returnUrl", Request.Url.PathAndQuery)
        <input type="submit" value="+放入購物車"/>
    }
</div>

點擊"+放入購物車"按鈕,調用HomeController中的AddToCart方法,並且需要把選購產品頁的url以query string的形式傳遞給控制器方法。

        //購物車頁
        public ActionResult CartIndex(Cart cart, string returnUrl)
        {
            return View(new CartIndexVm
            {
                Cart = cart,
                ReturnUrl = returnUrl
            });
        }
        //添加到購物車
        public ActionResult AddToCart(Cart cart, int id, string returnUrl)
        {
            Product product = GetAllProducts().Where(p => p.Id == id).FirstOrDefault();
            if (product != null)
            {
                cart.AddItem(product, 1);
            }
            return RedirectToAction("CartIndex", new {returnUrl});
        }

購物車頁Home/CartIndex.cshtml是一個CartIndexVm的強類型視圖頁。

@model MvcApplication1.Models.CartIndexVm
@{
    ViewBag.Title = "CartIndex";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
@section styles
{
    <link href="~/Content/shopitem.css" rel="external nofollow"  rel="stylesheet" />
    <link href="~/Content/jquery.bootstrap-touchspin.min.css" rel="external nofollow"  rel="stylesheet" />
}
<div class="container">
    <div class="row">
      @for (int i = 0; i < Model.Cart.Lines.Count(); i++)
      {
          var item = (Model.Cart.Lines.ToList())[i];
          if (i != 0 && i%4 == 0) //每行有4個div
          {
              <div style="clear:both;"></div>
          }
          
          <div class="col-md-3 column productbox">
                 
                <img src="@item.Product.ImageUrl" style="width: 460px; height: 250px;" class="img-responsive">
                <div class="producttitle">
                    <div class="productname">@item.Product.Name</div>
                    <div class="productdes">@item.Product.Description</div>
                    <div>
                        <table>
                            <tr>
                                <td style="width:50px;">單價:</td>
                                <td>@item.Product.Price</td>
                            </tr>
                            <tr>
                                <td>數量:</td>
                                <td>
                                    <input class="demo2" type="text" value="@item.Quantity" name="demo2" />
                                </td>
                            </tr>
                            <tr>
                                <td>小計:</td>
                                <td>@((item.Quantity * item.Product.Price).ToString("c"))</td>
                            </tr>
                        </table>
                    </div>
                </div>
                <div class="productprice">
                    <div class="text-center">
                        @using (Html.BeginForm("RemoveFromCart", "Home"))
                        {
                            @Html.Hidden("Id", item.Product.Id)
                            @Html.HiddenFor(x => x.ReturnUrl)
                            <input class="btn btn-default btn-sm" type="submit" value="移除"/>
                            <a href="#" rel="external nofollow"  class="btn btn-danger btn-sm" role="button">查看</a>
                        }
                        
                    </div>
                </div>
            </div>
      }
              
    </div>
</div>
<hr/>
<div class="container">
    <div class="row">
        <div class="text-center" style="font-size: 55px;font-weight: bold;color: red;">
           <span>總計:</span> @Model.Cart.ComputeTotalPrice().ToString("c")
        </div>
        <p align="left" class="actionButtons" style="width: 100%; clear: both">
            <a href="@Model.ReturnUrl" rel="external nofollow" >繼續購物</a>
        </p>
    </div>
</div>
@section scripts
{
    <script src="~/Scripts/jquery.bootstrap-touchspin.min.js"></script>
    <script type="text/javascript">
        $(function () {
            var i = $("input[class='demo2']");
            i.TouchSpin({
                min: 1,
                max: 100,
                step: 1//增量或減量
            });
            i.on("touchspin.on.stopupspin", function () {
                $.post('@Url.Action("IncreaseOrDecreaseOne", "Home")', { "id": $(this).closest("div.productbox").find('#Id').val(), "quantity": $(this).val() }, function (data) {
                    if (data.msg) {
                        location.reload();
                    }
                });
                //var temp = $(this).val();
                //alert(temp);
                //var temp = $(this).closest("div.productbox").find('#Id').val();
                //alert(temp);
            });
            i.on("touchspin.on.stopdownspin", function () {
                $.post('@Url.Action("IncreaseOrDecreaseOne", "Home")', { "id": $(this).closest("div.productbox").find('#Id').val(), "quantity": $(this).val() }, function (data) {
                    if (data.msg) {
                        location.reload();
                    }
                });
            });
        });
    </script>
}

在購物車頁,用瞭Bootstrap TouchSpin這款插件,點擊其中的數量的增量和減量按鈕,就向Home控制器中的IncreaseOrDecreaseOne方法發送一個異步post請求,得到返回數據刷新購物車頁。

       //點擊數量+號或點擊數量-號或自己輸入一個值
        [HttpPost]
        public ActionResult IncreaseOrDecreaseOne(Cart cart, int id, int quantity) 
        {
            Product product = GetAllProducts().Where(p => p.Id == id).FirstOrDefault();
            if (product != null)
            {
                cart.IncreaseOrDecreaseOne(product, quantity);
            }
            return Json(new
            {
                msg = true
            });
        }

在購車頁,點擊"移除"按鈕,就向Home控制器的RemoveFromCart方法提交表單。

        //從購物車移除
        public ActionResult RemoveFromCart(Cart cart, int id, string returnUrl)
        {
            Product product = GetAllProducts().Where(p => p.Id == id).FirstOrDefault();
            if (product != null)
            {
                cart.RemoveLine(product);
            }
            return RedirectToAction("CartIndex", new {returnUrl});
        }

購物車摘要是通過在Views/Shared/_Layout.cshtml中加載部分視圖而來。

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
    <link href="~/bootstrap/css/bootstrap.min.css" rel="external nofollow"  rel="stylesheet" />
    @RenderSection("styles", required: false)
    @Scripts.Render("~/bundles/jquery")
    <script src="~/bootstrap/js/bootstrap.min.js"></script>
</head>
<body>
    @{Html.RenderAction("Summary", "Home");}
    @RenderBody()
    @RenderSection("scripts", required: false)
</body>

在Home控制器中,對應的Summary方法為:

        //清空購物車
        public ActionResult EmptyCart(Cart cart, string returnUrl)
        {
            cart.Clear();
            return View("Index",new ProductsListVm{Products = GetAllProducts()});
        }
        //顯示購物車摘要
        public ActionResult Summary(Cart cart)
        {
            return View(cart);
        }

Home/Summary.cshtml是一個有關Cart的強類型部分視圖:

@model MvcApplication1.Models.Cart
@{
    Layout = null;
}
<div id="cart" style="background-color: #e3e3e3;padding: 10px; text-align:center;">
    <span class="caption">
        <b>購物車明細:</b>
        @if (Model != null)
        {
            @Model.Lines.Sum(x => x.Quantity) <span>件,</span>
            @Model.ComputeTotalPrice().ToString("c")
        }
        
    </span>
    
    @Html.ActionLink("結算", "CartIndex", "Home", new {returnUrl = Request.Url.PathAndQuery}, null)
    &nbsp;
    @Html.ActionLink("清空", "EmptyCart", "Home", new {returnUrl = Request.Url.PathAndQuery}, null)
</div>

註意:需要把Layout設置為null,否則會報錯,因為產品選購頁和購物車摘要同時加載Views/Shared/_Layout.cshtml就反復調用瞭。

到此這篇關於ASP.NET MVC實現橫向展示購物車的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: