在产品展示中,通常涉及产品的展示方式、查询、排序、分页,本篇就在ASP.NET MVC下,使用Boostrap来实现。
源码放在了GitHub:
先上效果图:
最上面是搜索和排序,每次点击搜索条件、排序,或者删除搜索条件都会触发异步加载。
中间部分为产品展示,提供了列表和格子这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的集合。
展开 public class Database { public static IEnumerableGetProducts() { return new List () { new Product(){Id = 1, Name = "羽绒服新时尚",Category = "服饰",Brand = "南极人",Age = 1, ImageUrl = "~/images/1.jpg",Price = 85m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 2, Name = "最新鲜潮货",Category = "服饰",Brand = "初语",Age = 2, ImageUrl = "~/images/2.jpg",Price = 95m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 3, Name = "新鲜态度",Category = "服饰",Brand = "文艺",Age = 3, ImageUrl = "~/images/3.jpg",Price = 105m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 4, Name = "儿童保暖内衣",Category = "服饰",Brand = "南极人",Age = 4, ImageUrl = "~/images/4.jpg",Price = 115m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 5, Name = "韩版蝴蝶结",Category = "服饰",Brand = "南极人",Age = 5, ImageUrl = "~/images/5.jpg",Price = 125m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 6, Name = "时尚童装加绒",Category = "服饰",Brand = "南极人",Age = 6, ImageUrl = "~/images/6.jpg",Price = 135m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 7, Name = "棉质儿童短袜",Category = "服饰",Brand = "南极人",Age = 7, ImageUrl = "~/images/7.jpg",Price = 145m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 8, Name = "灯芯绒打底单裤",Category = "服饰",Brand = "南极人",Age = 8, ImageUrl = "~/images/8.jpg",Price = 155m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 9, Name = "公主范裤子",Category = "服饰",Brand = "南极人",Age = 9, ImageUrl = "~/images/9.jpg",Price = 165m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 10, Name = "儿童百搭潮流",Category = "服饰",Brand = "南极人",Age = 10, ImageUrl = "~/images/10.jpg",Price = 175m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 11, Name = "童装牛仔裤",Category = "服饰",Brand = "南极人",Age = 11, ImageUrl = "~/images/11.jpg",Price = 185m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 12, Name = "吸汗条纹袜",Category = "服饰",Brand = "南极人",Age = 12, ImageUrl = "~/images/12.jpg",Price = 195m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 13, Name = "秋衣秋裤",Category = "服饰",Brand = "南极人",Age = 13, ImageUrl = "~/images/13.jpg",Price = 205m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 14, Name = "全棉棉毛套",Category = "服饰",Brand = "南极人",Age = 14, ImageUrl = "~/images/14.jpg",Price = 215m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 15, Name = "卡内衣套装",Category = "服饰",Brand = "南极人",Age = 15, ImageUrl = "~/images/15.jpg",Price = 215m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 16, Name = "保暖内衣套装",Category = "服饰",Brand = "南极人",Age = 16, ImageUrl = "~/images/16.jpg",Price = 225m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 17, Name = "精纱全棉内衣",Category = "服饰",Brand = "南极人",Age = 17, ImageUrl = "~/images/17.jpg",Price = 235m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 18, Name = "真我香水EDP",Category = "香水",Brand = "迪奥",Age = 18, ImageUrl = "~/images/18.jpg",Price = 245m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 19, Name = "晶钻粉钻香恋",Category = "香水",Brand = "范思哲",Age = 19, ImageUrl = "~/images/19.jpg",Price = 255m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"}, new Product(){Id = 20, Name = "绿邂逅清新",Category = "香水",Brand = "香奈儿",Age = 20, ImageUrl = "~/images/20.jpg",Price = 235m,Description = "产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述产品描述"} }; } public static IEnumerable GetPageProducts(ProductQuery query, out int total) { var allProducts = GetProducts(); if (!string.IsNullOrEmpty(query.BrandName)) { allProducts = allProducts.Where(p => p.Brand == query.BrandName); } if (!string.IsNullOrEmpty(query.CategoryName)) { allProducts = allProducts.Where(p => p.Category == query.CategoryName); } if (!string.IsNullOrEmpty(query.Age)) { int intAge = int.Parse(query.Age); allProducts = allProducts.Where(p => p.Age == intAge); } if (!string.IsNullOrEmpty(query.LowPrice) && !string.IsNullOrEmpty(query.HighPrice)) { decimal lowerPrice = decimal.Parse(query.LowPrice); decimal higherPrice = decimal.Parse(query.HighPrice); allProducts = allProducts.Where(p => p.Price >= lowerPrice && p.Price <= higherPrice); } if (!string.IsNullOrEmpty(query.LowPrice) && string.IsNullOrEmpty(query.HighPrice)) { decimal lowerPrice = decimal.Parse(query.LowPrice); allProducts = allProducts.Where(p => p.Price <= lowerPrice); } if (string.IsNullOrEmpty(query.LowPrice) && !string.IsNullOrEmpty(query.HighPrice)) { decimal higherPrice = decimal.Parse(query.HighPrice); allProducts = allProducts.Where(p => p.Price >= higherPrice); } total = allProducts.Count(); if (query.PaiXu == (short) AscDescEnum.asc) { allProducts = allProducts .OrderBy(p => p.Price) .Skip(query.PageSize*(query.PageIndex - 1)) .Take(query.PageSize); } else { allProducts = allProducts .OrderByDescending(p => p.Price) .Skip(query.PageSize * (query.PageIndex - 1)) .Take(query.PageSize); } return allProducts; } }
在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.Brand
into g
select 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.Category
into g
select 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.Age
into g
select 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必须具备:
@ViewBag.Title
@Styles.Render("~/Content/css")
@RenderSection("styles", required: false)
@Scripts.Render("~/bundles/jquery")
@RenderBody()
@RenderSection("scripts", required: false)
在Home/Index.cshtml中:
○ 用到了有关分页的一个jQuery插件http://botmonster.com/jquery-bootpag/
○ 页面首次记载,异步加载产品的前6条记录作为第一页○ 页面首次加载,异步加载所有分类作为搜索条件○ 页面首次加载,异步加载所有品牌作为搜索条件○ 页面首次加载,异步加载所有年限作为搜索条件○ 点击搜索条件中的品牌事件○ 点击搜索条件中的分类事件○ 点击搜索条件中的年限事件○ 点击搜索条件中的价格事件○ 点击"搜索条件"栏中的搜索标签事件
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@section styles
{
}
搜索条件
品牌
分类
价格
80元以下
80-90元
90-100元
100-110元
110-120元
120-130元
130-140元
140-150元
150-160元
160-170元
170-180元
180-190元
190-200元
200-210元
210-220元
220-230元
230元以上
年限
排序
价格∧
显示方式
列表
class="glyphicon glyphicon-th">格子
@section scripts
{
$(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);
}
});
}
${brand}
${category}
${age}
品牌:${pinpai} ×
分类:${fenlei} ×
价格:${price} ×
年限:${age} ×
{ { if rows}}
{ {each rows}}
${$value.Name}
品牌:${$value.Brand}
分类:${$value.Category}
年限:${$value.Age}
${$value.Description}
¥ ${$value.Price}
购买
{ {/each}}
{ { else}}
没有记录
{ {/if}}
}