通常,我們看到的購物車是這樣的:
雖然這種購物車顯示方式被廣泛運用,但我個人覺得不夠直觀。如果換成這樣呢?
本篇的源碼放在了: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="stylesheet" /><link href="~/Content/jquery.bootstrap-touchspin.min.css" 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="#" 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">繼續購物</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="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) @Html.ActionLink("清空", "EmptyCart", "Home", new {returnUrl = Request.Url.PathAndQuery}, null)</div>
注意:需要把Layout設置為null,否則會報錯,因為產品選購頁和購物車摘要同時加載Views/Shared/_Layout.cshtml就反復調用了。
其它方面,參考這里。