在产品展示中,通常涉及产品的展示方式、查询、排序、分页,本篇就在ASP.NET MVC下,使用Boostrap来实现。
源码放在了GitHub: https://github.com/darrenji/ProductsSearchSortPage
先上效果图:
最上面是搜索和排序,每次点击搜索条件、排序,或者删除搜索条件都会触发异步加载。
中间部分为产品展示,提供了列表和格子这2种显示方式。
最下方为分页。
能实现的功能包括:
○ 点击某一个搜索条件,该搜索条件被选中,选中项以标签的形式显示到"搜索条件"栏中,触发异步加载
○ 点击排序条件,该排序条件被选中,触发异步加载
○ 删除"搜索条件"栏中的搜索条件,触发异步加载
实现的思路大致是:
○ 搜索、排序区域是Bootstrap的表格
○ 产品展示、及切换2中展示方式都借助Boostrap来实现
○ 分页导航部分同样借助Bootstrap来实现
○ 搜索条件的显示是通过把异步加载到的数据填充到tmpl模版,然后追加到页面对应区域
○ 产品展示同样通过tmpl模版实现
○ 分页导航用到了jquery的一个分页插件,后面介绍
○ 每一个搜索条件、排序条件都有对应的隐藏域,当触发页面事件,就把值放在隐藏域中后,再传递给controller
产品模型 Models/Product.cs
public class Product{public int Id { get; set; }public string Name { get; set; }public string Description { get; set; }public string Category { get; set; }public string Brand { get; set; }public decimal Price { get; set; }public string ImageUrl { get; set; }public int Age { get; set; }}
关于搜索排序分页的基类 Models/QueryBase.cs
public class QueryBase{public int PageIndex { get; set; }public int PageSize { get; set; }public short PaiXu { get; set; }}
产品的搜索排序分页派生于QueryBase这个基类 Models/ProductQuery.cs
public class ProductQuery : QueryBase{public string CategoryName { get; set; }public string BrandName { get; set; }public string Age { get; set; }public string LowPrice { get; set; }public string HighPrice { get; set; }}
提供了一个有关排序的枚举 Models/AscDescEnum.cs
public enum AscDescEnum{asc = 0,desc = 1}
模拟一个数据库访问层,提供2个方法,一个方法获取所有的Product集合,另一个方法根据ProductQuery获取Product的集合。
展开
在HomeController中:
○ 提供一个action方法返回有关类别的json对象
○ 提供一个action方法返回有关品牌的json对象
○ 提供一个action方法返回有关年限的json对象
○ 提供一个action方法返回有关产品第一页的json对象
○ 提供一个action方法,根据搜索、排序、分页条件返回json对象
public class HomeController : Controller{public ActionResult Index()
{return View();
}//品牌
public ActionResult GetBrandsJson()
{var allProducts = Database.GetProducts();var result = from p in allProducts
group p by p.Brandinto gselect new {brand = g.Key};
return Json(result, JsonRequestBehavior.AllowGet);
}//类别
public ActionResult GetCategoriesJson()
{var allProducts = Database.GetProducts();var result = from p in allProducts
group p by p.Categoryinto gselect new {category = g.Key};
return Json(result, JsonRequestBehavior.AllowGet);
}//年限
public ActionResult GetAgesJson()
{var allProducts = Database.GetProducts();var result = from p in allProducts
group p by p.Ageinto gselect new { age = g.Key };
return Json(result, JsonRequestBehavior.AllowGet);
}//加载产品第一页
private string _categoryName = string.Empty;private string _brandName = string.Empty;private string _age = string.Empty;private string _lowerPrice = string.Empty;private string _higherPrice = string.Empty;public ActionResult GetFirstPage()
{var temp = new ProductQuery()
{PageIndex = 1,PageSize = 6,Age = _age,BrandName = _brandName,CategoryName = _categoryName,HighPrice = _higherPrice,LowPrice = _lowerPrice,PaiXu = (short)AscDescEnum.asc
};int totalNum = 0;
var allProducts = Database.GetPageProducts(temp, out totalNum);
var result = from p in allProducts
select new {p.Name, p.Brand, p.Category, p.Age, p.Description, p.Price};
var tempTotal = Convert.ToInt32(Math.Ceiling((double)(totalNum / 6))) +1;
var jsonResult = new { total = tempTotal, rows = result };
return Json(jsonResult, JsonRequestBehavior.AllowGet);
}//根据搜索排序分页条件加载
[HttpPost]public ActionResult GetProductsBySearchSortPage(ProductQuery productQuery)
{int totalNum = 0;
var allProducts = Database.GetPageProducts(productQuery, out totalNum);
var result = from p in allProducts
select new { p.Name, p.Brand, p.Category, p.Age, p.Description, p.Price };
var tempTotal = Convert.ToInt32(Math.Ceiling((double)(totalNum / 6))) + 1;
var jsonResult = new { total = tempTotal, rows = result };
return Json(jsonResult);
}}
在Shared/Layout.cshtml中,相关的css.js必须具备:
<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>@RenderBody()@RenderSection("scripts", required: false)</body
在Home/Index.cshtml中:
○ 用到了有关分页的一个jQuery插件http://botmonster.com/jquery-bootpag/
○ 页面首次记载,异步加载产品的前6条记录作为第一页
○ 页面首次加载,异步加载所有分类作为搜索条件
○ 页面首次加载,异步加载所有品牌作为搜索条件
○ 页面首次加载,异步加载所有年限作为搜索条件
○ 点击搜索条件中的品牌事件
○ 点击搜索条件中的分类事件
○ 点击搜索条件中的年限事件
○ 点击搜索条件中的价格事件
○ 点击"搜索条件"栏中的搜索标签事件
@{ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}@section styles{<link href="~/Content/ProductList.css" rel="stylesheet" />}<div class="container"><!--搜索条件开始--><div class="row" id="searches"><div class="span12"><table class="table table-condensed table-hover" id="serachtable"><tbody><tr><td class="fristcell">搜索条件</td><td class="secondcontent" id="sccondition"><ul class="tagul"></ul><input type="hidden" value="" name="brand"/><input type="hidden" value="" name="category"/><input type="hidden" value="" name="lowprice"/><input type="hidden" value="" name="highprice"/><input type="hidden" value="" name="age"/><input type="hidden" value="0" name="pricesort"/></td></tr><tr><td class="fristcell">品牌</td><td class="secondcontent" id="pp"></td></tr><tr><td class="fristcell">分类</td><td class="secondcontent" id="fl"></td></tr><tr><td class="fristcell">价格</td><td class="secondcontent" id="jg"><a class="innera" href="javascript:void(0)" lowprice="80" highprice="">80元以下</a><a class="innera" href="javascript:void(0)" lowprice="80" highprice="90">80-90元</a><a class="innera" href="javascript:void(0)" lowprice="90" highprice="100">90-100元</a><a class="innera" href="javascript:void(0)" lowprice="100" highprice="110">100-110元</a><a class="innera" href="javascript:void(0)" lowprice="110" highprice="120">110-120元</a><a class="innera" href="javascript:void(0)" lowprice="120" highprice="130">120-130元</a><a class="innera" href="javascript:void(0)" lowprice="130" highprice="140">130-140元</a><a class="innera" href="javascript:void(0)" lowprice="140" highprice="150">140-150元</a><a class="innera" href="javascript:void(0)" lowprice="150" highprice="160">150-160元</a><a class="innera" href="javascript:void(0)" lowprice="160" highprice="170">160-170元</a><a class="innera" href="javascript:void(0)" lowprice="170" highprice="180">170-180元</a><a class="innera" href="javascript:void(0)" lowprice="180" highprice="190">180-190元</a><a class="innera" href="javascript:void(0)" lowprice="190" highprice="200">190-200元</a><a class="innera" href="javascript:void(0)" lowprice="200" highprice="210">200-210元</a><a class="innera" href="javascript:void(0)" lowprice="210" highprice="220">210-220元</a><a class="innera" href="javascript:void(0)" lowprice="220" highprice="230">220-230元</a><a class="innera" href="javascript:void(0)" lowprice="" highprice="230">230元以上</a></td></tr><tr><td class="fristcell">年限</td><td class="secondcontent" id="nx"></td></tr><tr><td class="fristcell">排序</td><td class="secondcontent" id="px"><a class="innera" href="javascript:void(0)" id="pricesort">价格<span style="margin: 0px;">∧</span><span style="margin: 0px;display: none">∨</span></a></td></tr><tr><td></td><td></td></tr></tbody></table></div></div><!--搜索条件结束--><!--产品开始--><div class="container"><div class="well well-sm"><strong>显示方式</strong><div class="btn-group"><a href="#" id="list" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-th-list"></span>列表</a> <a href="#" id="grid" class="btn btn-default btn-sm"><spanclass="glyphicon glyphicon-th"></span>格子</a></div></div><div id="products" class="row list-group"></div></div><!--产品结束--><!--分页开始--><div class="row" id="page-selection" style="text-align: center;margin-bottom: 50px;"></div><!--分页结束--></div>@section scripts{<script src="~/Scripts/jquery.tmpl.min.js"></script>
<script src="~/Scripts/jquery.bootpag.min.js"></script>
<script type="text/javascript">
$(function () {//加载首页产品
$.getJSON('@Url.Action("GetFirstPage","Home")', function(data) {if (data) {
$('#productTemplate').tmpl(data).appendTo('#products');//关于分页
$('#page-selection').bootpag({total: data.total, //初始显示的页数
maxVisible: 10}).on("page", function (event, num) { //点击分页按钮var productQueryObject = {categoryName: $('#sccondition').find("input[name='category']").val(),
brandName: $('#sccondition').find("input[name='brand']").val(),
age: $('#sccondition').find("input[name='age']").val(),
lowPrice: $('#sccondition').find("input[name='lowprice']").val(),
highPrice: $('#sccondition').find("input[name='highprice']").val(),
pageIndex: num,pageSize: 6,paiXu: $('#sccondition').find("input[name='pricesort']").val()
};$.ajax({type: "POST",
url: '@Url.Action("GetProductsBySearchSortPage","Home")',dataType: "json",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(productQueryObject),success: function (result) {$('#products').empty();$('#productTemplate').tmpl(result).appendTo('#products');//maxVisible 最多可见的页数
$(this).bootpag({ total: result.total});
},error: function (error) {alert("有错误: " + error.responseText);
}});});}});//加载所有品牌
$.getJSON('@Url.Action("GetBrandsJson", "Home")', function (data) {$('#pinpaiTemplate').tmpl(data).appendTo('#pp');});//点击某一品牌
$('#pp').on("click", ".innera", function () {//先清空其它已经存在与搜索区域的品牌
$('ul.tagul li').find('.pinpaitag').parent().hide();//清空搜索区域中有关品牌的隐藏域
$('#sccondition').find("input[name='brand']").val('');
//当前a以外的为不选中状态
$('#pp').find('.innera').removeClass('selected');//当前a为选中状态
$(this).addClass('selected');
//填充模版并追加到搜索区域
$('#pinpaitagTemplate').tmpl({ pinpai: $(this).text() }).appendTo('ul.tagul');
//为搜索区域中有关品牌的隐藏域赋值
$('#sccondition').find("input[name='brand']").val($(this).text());getProductsBySortOrSearch();});//加载所有类别
$.getJSON('@Url.Action("GetCategoriesJson", "Home")', function(data) {$('#leibieTemplate').tmpl(data).appendTo('#fl');});//点击某一类别
$('#fl').on("click", ".innera", function () {//先清空其它已经存在与搜索区域的类别
$('ul.tagul li').find('.fenleitag').parent().hide();//清空搜索区域中有关类别的隐藏域
$('#sccondition').find("input[name='category']").val('');
//当前a以外的为不选中状态
$('#fl').find('.innera').removeClass('selected');//当前a为选中状态
$(this).addClass('selected');
//填充模版并追加到搜索区域
$('#fenleitagTemplate').tmpl({ fenlei: $(this).text() }).appendTo('ul.tagul');
//为搜索区域中有关类别的隐藏域赋值
$('#sccondition').find("input[name='category']").val($(this).text());getProductsBySortOrSearch();});//加载所有Age
$.getJSON('@Url.Action("GetAgesJson", "Home")', function(data) {$('#ageTemplate').tmpl(data).appendTo('#nx');});//点击某一年限
$('#nx').on("click", ".innera", function () {//先清空其它已经存在与搜索区域的年限
$('ul.tagul li').find('.agetag').parent().hide();//清空搜索区域中有关年限的隐藏域
$('#sccondition').find("input[name='age']").val('');
//当前a以外的为不选中状态
$('#nx').find('.innera').removeClass('selected');//当前a为选中状态
$(this).addClass('selected');
//填充模版并追加到搜索区域
$('#agetagTemplate').tmpl({ age: $(this).text() }).appendTo('ul.tagul');
//为搜索区域中有关年限的隐藏域赋值
$('#sccondition').find("input[name='age']").val($(this).text());getProductsBySortOrSearch();});//点击某一价格
$('#jg').on("click", ".innera", function () {//先清空其它已经存在与搜索区域的年限
$('ul.tagul li').find('.pricetag').parent().hide();//清空搜索区域中有关价格的隐藏域
$('#sccondition').find("input[name='lowprice']").val('');
$('#sccondition').find("input[name='highprice']").val('');
//当前a以外的为不选中状态
$('#jg').find('.innera').removeClass('selected');//当前a为选中状态
$(this).addClass('selected');
//填充模版并追加到搜索区域
$('#pricetagTemplate').tmpl({ price: $(this).text() }).appendTo('ul.tagul');
//为搜索区域中有关价格的隐藏域赋值
$('#sccondition').find("input[name='lowprice']").val($(this).attr('lowprice'));$('#sccondition').find("input[name='highprice']").val($(this).attr('highprice'));getProductsBySortOrSearch();});//关于产品列表
$('#list').click(function(event) {
event.preventDefault();
$('#products .item').addClass('list-group-item');});//关于产品方格展示
$('#grid').click(function(event) {
event.preventDefault();
$('#products .item').removeClass('list-group-item');$('#products .item').addClass('grid-group-item');});//点击搜索标签删除
$('ul.tagul').on("click", "li span", function () {//获取当前span的class值
var temp = $(this).attr('class');if (temp == "tagcontent pinpaitag") {//把品牌中的所有a都设为不选中状态
$('#pp').find('.innera').removeClass('selected');//清空搜索区域中有关品牌的隐藏域
$('#sccondition').find("input[name='brand']").val('');
} else if (temp == "tagcontent fenleitag") {//把分类中的所有a都设为不选中状态
$('#fl').find('.innera').removeClass('selected');//清空搜索区域中有关分类的隐藏域
$('#sccondition').find("input[name='category']").val('');
} else if (temp == "tagcontent agetag") {//把年限中的所有a都设为不选中状态
$('#nx').find('.innera').removeClass('selected');//清空搜索区域中有关年限的隐藏域
$('#sccondition').find("input[name='age']").val('');
} else if (temp == "tagcontent pricetag") {//把价格中的所有a都设为不选中状态
$('#jg').find('.innera').removeClass('selected');//清空搜索区域中有关价格的隐藏域
$('#sccondition').find("input[name='lowprice']").val('');
$('#sccondition').find("input[name='highprice']").val('');
}$(this).parent().hide();
getProductsBySortOrSearch();});//鼠标移上搜索标签
$('ul.tagul').on("mouseover", "li span", function() {$(this).css('cursor', 'pointer');
$(this).css("background-color", "orangered");});//鼠标移去搜索标签
$('ul.tagul').on("mouseout", "li span", function() {$(this).css('cursor', 'default');$(this).css("background-color", "#5BC0DE");});//点击排序中的价格排序
$('#pricesort').on("click", function() {
$(this).find("span").toggle();var temp = $('#sccondition').find("input[name='pricesort']");
temp.val(temp.val() == '0' ? '1' : '0');getProductsBySortOrSearch();});});//点击搜索条件或者升序降序,当前页为1
function getProductsBySortOrSearch() {var productQueryObject = {categoryName: $('#sccondition').find("input[name='category']").val(),
brandName: $('#sccondition').find("input[name='brand']").val(),
age: $('#sccondition').find("input[name='age']").val(),
lowPrice: $('#sccondition').find("input[name='lowprice']").val(),
highPrice: $('#sccondition').find("input[name='highprice']").val(),
pageIndex: 1,pageSize: 6,paiXu: $('#sccondition').find("input[name='pricesort']").val()
};$.ajax({type: "POST",
url: '@Url.Action("GetProductsBySearchSortPage","Home")',dataType: "json",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(productQueryObject),success: function (data) {$('#products').empty();$('#productTemplate').tmpl(data).appendTo('#products');//关于分页
$('#page-selection').bootpag({total: data.total, //初始显示的页数
maxVisible: 10}).on("page", function (event, num) { //点击分页按钮var productQueryObject = {categoryName: $('#sccondition').find("input[name='category']").val(),
brandName: $('#sccondition').find("input[name='brand']").val(),
age: $('#sccondition').find("input[name='age']").val(),
lowPrice: $('#sccondition').find("input[name='lowprice']").val(),
highPrice: $('#sccondition').find("input[name='highprice']").val(),
pageIndex: num,pageSize: 6,paiXu: $('#sccondition').find("input[name='pricesort']").val()
};$.ajax({type: "POST",
url: '@Url.Action("GetProductsBySearchSortPage","Home")',dataType: "json",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(productQueryObject),success: function (result) {$('#products').empty();$('#productTemplate').tmpl(result).appendTo('#products');//maxVisible 最多可见的页数
$(this).bootpag({ total: result.total });
},error: function (error) {alert("有错误: " + error.responseText);
}});});},error: function (error) {alert("有错误: " + error.responseText);
}});}</script><!--品牌搜索模版--><script id="pinpaiTemplate" type="text/x-jQuery-tmpl"><a class="innera" href="javascript:void(0)">${brand}</a></script><!--类别搜索模版--><script id="leibieTemplate" type="text/x-jQuery-tmpl"><a class="innera" href="javascript:void(0)">${category}</a></script><!--年限搜索模版--><script id="ageTemplate" type="text/x-jQuery-tmpl"><a class="innera" href="javascript:void(0)">${age}</a></script><!--品牌标签模版--><script id="pinpaitagTemplate" type="text/x-jQuery-tmpl"><li><span class="tagcontent pinpaitag">品牌:${pinpai} ×</span></li></script><!--分类标签模版--><script id="fenleitagTemplate" type="text/x-jQuery-tmpl"><li><span class="tagcontent fenleitag">分类:${fenlei} ×</span></li></script><!--价格标签模版--><script id="pricetagTemplate" type="text/x-jQuery-tmpl"><li><span class="tagcontent pricetag">价格:${price} ×</span></li></script><!--年限标签模版--><script id="agetagTemplate" type="text/x-jQuery-tmpl"><li><span class="tagcontent agetag">年限:${age} ×</span></li></script><!--产品列表模版--><script id="productTemplate" type="text/x-jQuery-tmpl">{{if rows}}
{{each rows}}<div class="item col-xs-4 col-lg-4"><div class="thumbnail"><img class="group list-group-image" src="http://placehold.it/400x250/000/fff" alt="" /><div class="caption"><h4 class="group inner list-group-item-heading">${$value.Name}</h4>
<p class="group inner list-group-item-text">品牌:${$value.Brand}</p>
<p class="group inner list-group-item-text">分类:${$value.Category}</p>
<p class="group inner list-group-item-text">年限:${$value.Age}</p>
<p class="group inner list-group-item-text">${$value.Description}</p>
<div class="row"><div class="col-xs-12 col-md-6"><p class="lead">¥ ${$value.Price}</p>
</div><div class="col-xs-12 col-md-6"><a class="btn btn-success " href="javascript:void(0)">购买</a></div></div></div></div></div>{{/each}}{{else}}
<span>没有记录</span>{{/if}}
</script>}