在需要处理很多分类以及导航的时候,树形导航菜单就比较适合。例如在汽车之家上:
页面主要分两部分,左边是导航菜单,右边显示对应的内容。现在,我们就在ASP.NET MVC 4 下临摹一个,如下:
实现的效果包括:
1、点击导航菜单上的品牌,左侧显示该品牌下的所有车型。
2、点击导航菜单上的车系,左侧显示该车系下的所有车型。
3、点击左侧上方的字母导航,锚点跳到导航菜单的对应部分。
4、页面加载完毕,显示所有品牌和车系,即树形导航完全展开。
5、点击导航菜单上的品牌,收缩或展开对应的车系,收缩时,品牌前面图标为+号,展开时,品牌前面的图片为-号。
源码部分,在这里。
思路呢?
页面分成左右2部分,使用Bootstrap轻松实现:
<div class="row"> | |
<div class="col-md-2 col-lg-2 col-sm-2"> | |
</div> | |
<div class="col-md-10 col-lg-10 col-sm-10"> | |
</div> | |
</div> |
左侧最上方的字母导航,被放在一个div中,页面加载的时候向控制器动态请求。
品牌上方的字母归类,比如奥迪上方的字母A,实际上是一个div。
品牌和车系放在了ul中,比如奥迪品牌以及奥迪下的奥迪A4和奥迪A6车系。车系被放在了dl中。
树形菜单采用模版比较合适,先把数据填充到模版,再把模版追加到页面元素。
当点击左侧树形导航上的品牌或车系,右侧通过iframe来呈现对应的内容。
领域先行。有关品牌和车系就抽象成如下的类:
public class CarCategory | |
{ | |
public int Id { get; set; } | |
public int ParentId { get; set; } | |
public string Name { get; set; } | |
public string FirstLetter { get; set; } | |
public string AnchorName { get; set; } | |
public int Level { get; set; } | |
public short DelFlag { get; set; } | |
} |
有关车型就抽象成如下的类:
public class Car | |
{ | |
public int Id { get; set; } | |
public int PinPaiId { get; set; } | |
public int CheXiId { get; set; } | |
public string Name { get; set; } | |
} |
页面左侧呈现树形导航需要向控制器请求json数据,大致格式是:
首字母
锚点名称
所有品牌
品牌编号
品牌名称
所有车系
车系编号
车系名称
车系下车型的总数量
貌似有3层,那就从最里面这层开始建模。有关车系在树形导航中的显示:
public class CheXiForDisplay | |
{ | |
public int CheXiId { get; set; } | |
public int TotalCount { get; set; } | |
public string CheXiName { get; set; } | |
} |
有关品牌在树形导航中的显示:
public class PinPaiForDisplay | |
{ | |
public int PinPaiId { get; set; } | |
public string PinPaiName { get; set; } | |
public List<CheXiForDisplay> CheXis { get; set; } | |
} |
有关品牌车系分组的:
public class PinPaiCheXiForDisplay | |
{ | |
public string FirstLetter { get; set; } | |
public string Anchor { get; set; } | |
public List<PinPaiForDisplay> PinPais { get; set; } | |
} |
数据源从哪里来?模拟了一个:
public class Database | |
{ | |
public static IEnumerable<CarCategory> GetAllCarCategories() | |
{ | |
return new List<CarCategory> | |
{ | |
new CarCategory(){Id = 1, ParentId = 0, Name = "奥迪",FirstLetter = "A", AnchorName = "AA", Level = 1, DelFlag = 0}, | |
new CarCategory(){Id = 2, ParentId = 0, Name = "宝马",FirstLetter = "B", AnchorName = "BB", Level = 1, DelFlag = 0}, | |
new CarCategory(){Id = 3, ParentId = 0, Name = "保时捷",FirstLetter = "B", AnchorName = "BB", Level = 1, DelFlag = 0}, | |
new CarCategory(){Id = 4, ParentId = 0, Name = "长安",FirstLetter = "C", AnchorName = "CC", Level = 1, DelFlag = 0}, | |
new CarCategory(){Id = 5, ParentId = 0, Name = "大众",FirstLetter = "D", AnchorName = "DD", Level = 1, DelFlag = 0}, | |
new CarCategory(){Id = 6, ParentId = 0, Name = "东风",FirstLetter = "D", AnchorName = "DD", Level = 1, DelFlag = 0}, | |
new CarCategory(){Id = 7, ParentId = 0, Name = "丰田",FirstLetter = "F", AnchorName = "FF", Level = 1, DelFlag = 0}, | |
new CarCategory(){Id = 8, ParentId = 0, Name = "福特",FirstLetter = "F", AnchorName = "FF", Level = 1, DelFlag = 0}, | |
new CarCategory(){Id = 9, ParentId = 1, Name = "奥迪A4",FirstLetter = "A", AnchorName = "AA", Level = 2, DelFlag = 0}, | |
new CarCategory(){Id = 10, ParentId = 1, Name = "奥迪A6",FirstLetter = "A", AnchorName = "AA", Level = 2, DelFlag = 0}, | |
new CarCategory(){Id = 11, ParentId = 2, Name = "宝马1",FirstLetter = "B", AnchorName = "BB", Level = 2, DelFlag = 0}, | |
new CarCategory(){Id = 12, ParentId = 2, Name = "宝马2",FirstLetter = "B", AnchorName = "BB", Level = 2, DelFlag = 0}, | |
new CarCategory(){Id = 13, ParentId = 3, Name = "保时捷1",FirstLetter = "B", AnchorName = "BB", Level = 2, DelFlag = 0}, | |
new CarCategory(){Id = 14, ParentId = 3, Name = "保时捷2",FirstLetter = "B", AnchorName = "BB", Level = 2, DelFlag = 0}, | |
new CarCategory(){Id = 15, ParentId = 4, Name = "长安1",FirstLetter = "C", AnchorName = "CC", Level = 2, DelFlag = 0}, | |
new CarCategory(){Id = 16, ParentId = 4, Name = "长安2",FirstLetter = "C", AnchorName = "CC", Level = 2, DelFlag = 0}, | |
new CarCategory(){Id = 17, ParentId = 5, Name = "大众1",FirstLetter = "D", AnchorName = "DD", Level = 2, DelFlag = 0}, | |
new CarCategory(){Id = 18, ParentId = 5, Name = "大众2",FirstLetter = "D", AnchorName = "DD", Level = 2, DelFlag = 1}, | |
new CarCategory(){Id = 19, ParentId = 6, Name = "东风1",FirstLetter = "D", AnchorName = "DD", Level = 2, DelFlag = 0}, | |
new CarCategory(){Id = 20, ParentId = 6, Name = "东风2",FirstLetter = "D", AnchorName = "DD", Level = 2, DelFlag = 0}, | |
new CarCategory(){Id = 21, ParentId = 7, Name = "丰田1",FirstLetter = "F", AnchorName = "FF", Level = 2, DelFlag = 0}, | |
new CarCategory(){Id = 22, ParentId = 7, Name = "丰田2",FirstLetter = "F", AnchorName = "FF", Level = 2, DelFlag = 0}, | |
new CarCategory(){Id = 23, ParentId = 8, Name = "福特1",FirstLetter = "F", AnchorName = "AFF", Level = 2, DelFlag = 0}, | |
new CarCategory(){Id = 24, ParentId = 8, Name = "福特2",FirstLetter = "F", AnchorName = "AFF", Level = 2, DelFlag = 0} | |
}; | |
} | |
public static IEnumerable<Car> GetAllCars() | |
{ | |
return new List<Car> | |
{ | |
new Car(){Id = 1, PinPaiId = 1, CheXiId = 9, Name = "奥迪A401"}, | |
new Car(){Id = 2, PinPaiId = 1, CheXiId = 9, Name = "奥迪A402"}, | |
new Car(){Id = 3, PinPaiId = 1, CheXiId = 10, Name = "奥迪A601"}, | |
new Car(){Id = 4, PinPaiId = 1, CheXiId = 10, Name = "奥迪A602"}, | |
new Car(){Id = 5, PinPaiId = 2, CheXiId = 11, Name = "宝马101"}, | |
new Car(){Id = 6, PinPaiId = 2, CheXiId = 11, Name = "宝马102"}, | |
new Car(){Id = 7, PinPaiId = 2, CheXiId = 12, Name = "宝马201"}, | |
new Car(){Id = 8, PinPaiId = 2, CheXiId = 12, Name = "宝马202"}, | |
new Car(){Id = 9, PinPaiId = 3, CheXiId = 13, Name = "保时捷101"}, | |
new Car(){Id = 10, PinPaiId = 3, CheXiId = 13, Name = "保时捷102"}, | |
new Car(){Id = 11, PinPaiId = 3, CheXiId = 14, Name = "保时捷201"}, | |
new Car(){Id = 12, PinPaiId = 3, CheXiId = 14, Name = "保时捷202"}, | |
new Car(){Id = 13, PinPaiId = 4, CheXiId = 15, Name = "长安101"}, | |
new Car(){Id = 14, PinPaiId = 4, CheXiId = 15, Name = "长安102"}, | |
new Car(){Id = 15, PinPaiId = 4, CheXiId = 16, Name = "长安201"}, | |
new Car(){Id = 16, PinPaiId = 4, CheXiId = 16, Name = "长安202"}, | |
new Car(){Id = 17, PinPaiId = 5, CheXiId = 17, Name = "大众101"}, | |
new Car(){Id = 18, PinPaiId = 5, CheXiId = 17, Name = "大众102"}, | |
new Car(){Id = 19, PinPaiId = 5, CheXiId = 18, Name = "大众201"}, | |
new Car(){Id = 20, PinPaiId = 5, CheXiId = 18, Name = "大众202"}, | |
new Car(){Id = 21, PinPaiId = 6, CheXiId = 19, Name = "东风101"}, | |
new Car(){Id = 22, PinPaiId = 6, CheXiId = 19, Name = "东风102"}, | |
new Car(){Id = 23, PinPaiId = 6, CheXiId = 20, Name = "东风201"}, | |
new Car(){Id = 24, PinPaiId = 6, CheXiId = 20, Name = "东风202"}, | |
new Car(){Id = 25, PinPaiId = 7, CheXiId = 21, Name = "丰田101"}, | |
new Car(){Id = 26, PinPaiId = 7, CheXiId = 21, Name = "丰田102"}, | |
new Car(){Id = 27, PinPaiId = 7, CheXiId = 22, Name = "丰田201"}, | |
new Car(){Id = 28, PinPaiId = 7, CheXiId = 22, Name = "丰田202"}, | |
new Car(){Id = 29, PinPaiId = 8, CheXiId = 23, Name = "福特101"}, | |
new Car(){Id = 30, PinPaiId = 8, CheXiId = 23, Name = "福特102"}, | |
new Car(){Id = 31, PinPaiId = 8, CheXiId = 24, Name = "福特201"}, | |
new Car(){Id = 32, PinPaiId = 8, CheXiId = 24, Name = "福特202"} | |
}; | |
} | |
} |
好,现在可以向控制器要数据了。
public class HomeController : Controller | |
{ | |
public ActionResult Index() | |
{ | |
return View(); | |
} | |
//获取所有首字母以及锚点的json | |
public ActionResult GetFirstLettersJson() | |
{ | |
var allCarCategories = Database.GetAllCarCategories(); | |
var result = from l in allCarCategories | |
group l by l.FirstLetter | |
into g | |
select new {firstletter = g.Key, anchor=g.ToList()[0].AnchorName}; | |
return Json(result, JsonRequestBehavior.AllowGet); | |
} | |
//获取按首字母分组后的品牌车系json | |
public ActionResult GetPinPaiCheXiJson() | |
{ | |
var allPinPais = Database.GetAllCarCategories().Where(c => c.Level == 1).OrderBy(c => c.FirstLetter); | |
var allPinPaisGroup = from p in allPinPais | |
group p by new | |
{ | |
p.FirstLetter, | |
p.AnchorName | |
}; | |
List<PinPaiCheXiForDisplay> result1 = new List<PinPaiCheXiForDisplay>(); | |
foreach (var item in allPinPaisGroup) | |
{ | |
//品牌车系 | |
PinPaiCheXiForDisplay pinPaiCheXiForDisplay = new PinPaiCheXiForDisplay(); | |
pinPaiCheXiForDisplay.FirstLetter = item.Key.FirstLetter; | |
pinPaiCheXiForDisplay.Anchor = item.Key.AnchorName; | |
//品牌 | |
List<PinPaiForDisplay> pinPaiForDisplays = new List<PinPaiForDisplay>(); | |
foreach (var pinpai in item.ToList()) | |
{ | |
PinPaiForDisplay pinPaiForDisplay = new PinPaiForDisplay(); | |
pinPaiForDisplay.PinPaiId = pinpai.Id; | |
pinPaiForDisplay.PinPaiName = pinpai.Name; | |
//车系 | |
List<CheXiForDisplay> cheXiForDisplays = new List<CheXiForDisplay>(); | |
var cheXis = Database.GetAllCarCategories().Where(c => c.ParentId == pinpai.Id).OrderBy(c => c.Id); | |
foreach (var chexi in cheXis) | |
{ | |
CheXiForDisplay cheXiForDisplay = new CheXiForDisplay(); | |
cheXiForDisplay.CheXiId = chexi.Id; | |
cheXiForDisplay.CheXiName = chexi.Name; | |
cheXiForDisplay.TotalCount = cheXis.Count(); | |
cheXiForDisplays.Add(cheXiForDisplay); | |
} | |
pinPaiForDisplay.CheXis = cheXiForDisplays; | |
pinPaiForDisplays.Add(pinPaiForDisplay); | |
} | |
pinPaiCheXiForDisplay.PinPais = pinPaiForDisplays; | |
result1.Add(pinPaiCheXiForDisplay); | |
} | |
return Json(result1, JsonRequestBehavior.AllowGet); | |
} | |
//根据品牌Id显示车型 | |
public ActionResult GetCheXingsByPId(int pid) | |
{ | |
var cars = Database.GetAllCars().Where(c => c.PinPaiId == pid); | |
return View(cars); | |
} | |
//根据车系Id显示车型 | |
public ActionResult GetCheXingsByChexiId(int cxid) | |
{ | |
var cars = Database.GetAllCars().Where(c => c.CheXiId == cxid); | |
return View(cars); | |
} | |
} |
在Shared/_Layout.cshtml中,该引用的css,js都要引用上。
<head> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width" /> | |
<title>@ViewBag.Title</title> | |
<link href="~/bootstrap/css/bootstrap.min.css" rel="external nofollow" rel="stylesheet" /> | |
Render("~/Content/css") | .|
"styles", required: false) | (|
Render("~/bundles/jquery") | .|
<script src="~/bootstrap/js/bootstrap.min.js"></script> | |
</head> | |
<body> | |
@RenderBody() | |
@RenderSection("scripts", required: false) | |
</body> |
Home/Index.cshtml就负责显示就行了。
@{ | |
ViewBag.Title = "Index"; | |
Layout = "~/Views/Shared/_Layout.cshtml"; | |
} | |
@section styles | |
{ | |
<link href="~/Content/sidemenu.css" rel="external nofollow" rel="stylesheet" /> | |
} | |
<div class="row"> | |
<div class="col-md-2 col-lg-2 col-sm-2"> | |
<!--字母导航开始--> | |
<div id="lDaoHang"> | |
</div> | |
<!--字母导航结束--> | |
<!--树开始--> | |
<div id="cTreeDiv" style="overflow-x: hidden; overflow-y: scroll; height: 550px; width: 99%;"> | |
</div> | |
<!--树结束--> | |
<div> | |
<dl id="test"></dl> | |
</div> | |
</div> | |
<div class="col-md-10 col-lg-10 col-sm-10"> | |
<div class="carContent" id="carContent"> | |
<iframe id="frameCar" src="" scrolling="no" frameborder="0" height="100%" width="100%" onload="this.height=this.contentWindow.document.documentElement.scrollHeight"></iframe> | |
</div> | |
</div> | |
</div> | |
@section scripts | |
{ | |
<script src="~/Scripts/jquery.tmpl.min.js"></script> | |
<script type="text/javascript"> | |
$(function () { | |
//加载首字母 | |
$.getJSON('@Url.Action("GetFirstLettersJson", "Home")', function (data) { | |
$('#firstLetterTemplate').tmpl(data).appendTo('#lDaoHang'); | |
}); | |
//加载所有品牌车系 | |
$.getJSON('@Url.Action("GetPinPaiCheXiJson", "Home")', function (data) { | |
$('#pinpaiTemplate').tmpl(data).appendTo('#cTreeDiv'); | |
$('.pLink').each(function () { | |
pinPaiInitialState($(this)); | |
}); | |
}); | |
//隐藏ifame所在div | |
$("#carContent").css("display", "none"); | |
//点击品牌 | |
$('#cTreeDiv').on("click", ".pLink", function () { | |
//切换 | |
togglePinPaiState($(this)); | |
//显示右边区域 | |
var url = "/Home/GetCheXingsByPId?pid=" + $(this).attr('id'); | |
$("#frameCar").attr("src", url); | |
$("#carContent").css("display", "block"); | |
}); | |
//点击车系 | |
$('#cTreeDiv').on("click", ".cxLink", function () { | |
//显示右边区域 | |
var url = "/Home/GetCheXingsByChexiId?cxid=" + $(this).attr('id'); | |
$("#frameCar").attr("src", url); | |
$("#carContent").css("display", "block"); | |
}); | |
}); | |
//品牌的初始状态,即把车系隐藏和品牌前面的图标为+号 | |
var pstate = 0; | |
//品牌只有2种状态:所有车系隐藏,品牌前面的图标变为为+号;要么显示品牌下的所有车系,品牌前面的图标变为-号 | |
//把车系隐藏和品牌前面的图标为+号,记为状态0 | |
//把车系隐藏和品牌前面的图标为-号,记为状态1 | |
function togglePinPaiState($pinpai) { | |
if (pstate == 0) { | |
var $i = $pinpai.parent().find("i"); | |
var attr = $i.attr('class'); | |
if (typeof attr !== typeof undefined && attr !== false) { | |
$i.removeClass("iconHide"); | |
} | |
$i.addClass("iconShow"); | |
$pinpai.parent().parent().find("dl").show(); | |
pstate = 1; | |
} else { | |
var $j = $pinpai.parent().find("i"); | |
var attr1 = $j.attr('class'); | |
if (typeof attr1 !== typeof undefined && attr1 !== false) { | |
$j.removeClass("iconShow"); | |
} | |
$j.addClass("iconHide"); | |
$pinpai.parent().parent().find("dl").hide(); | |
pstate = 0; | |
} | |
} | |
function pinPaiInitialState($pinpai) { | |
pstate = 0; | |
togglePinPaiState($pinpai); | |
} | |
</script> | |
<!--首字母模版--> | |
<script id="firstLetterTemplate" type="text/x-jQuery-tmpl"> | |
<div class="lWrapper"> | |
<a href="#${anchor}" rel="external nofollow" class="lLink">${firstletter}</a> | |
</div> | |
</script> | |
<!--品牌模版--> | |
<script id="pinpaiTemplate" type="text/x-jQuery-tmpl"> | |
<div class="lHeader" id="${Anchor}">${FirstLetter}</div> | |
<ul class="uTree"> | |
{{if PinPais}} | |
{{each PinPais}} | |
<li> | |
<h5 class="font-bold"> | |
<a href="javascript:void(0)" rel="external nofollow" rel="external nofollow" class="pLink" id="${$value.PinPaiId}"><i></i>${$value.PinPaiName}</a> | |
</h5> | |
<dl> | |
{{tmpl(CheXis) "#chexiTemplate"}} | |
</dl> | |
</li> | |
{{/each}} | |
{{else}} | |
<li>没有对应品牌</li> | |
{{/if}} | |
</ul> | |
</script> | |
<!--车系模版--> | |
<script id="chexiTemplate" type="text/x-jQuery-tmpl"> | |
<dd><a id="${CheXiId}" href="javascript:void(0)" rel="external nofollow" rel="external nofollow" class="cxLink">${CheXiName}<em>(${TotalCount})</em></a></dd> | |
</script> | |
} |
以上,
- 从控制器返回的有关树形菜单的json数据,先填充到jquery.tmpl.min.js模版中,然后追加到页面上。
- 树形菜单的展开或收缩,通过全局变量pstate在0和1之间的切换来实现,页面初次加载给变量pstate一个初始值。
另外,点击树形导航上的品牌,iframe加载的视图是Home/GetCheXingsByPId.cshtml
IEnumerable<MvcApplication1.Models.Car> | |
@{ | |
ViewBag.Title = "GetCheXingsByPId"; | |
Layout = "~/Views/Shared/_Layout.cshtml"; | |
} | |
<h2>GetCheXingsByPId</h2> | |
<div> | |
var item in Model) | (|
{ | |
<p> .Name </p> | |
} | |
</div> |
点击树形导航上的车系,iframe加载的视图是Home/GetCheXingsByChexiId.cshtml
IEnumerable<MvcApplication1.Models.Car> | |
@{ | |
ViewBag.Title = "GetCheXingsByChexiId"; | |
Layout = "~/Views/Shared/_Layout.cshtml"; | |
} | |
<h2>GetCheXingsByChexiId</h2> | |
<div> | |
var item in Model) | (|
{ | |
<p> .Name </p> | |
} | |
</div> |
就这样。