今天做一個管理後臺菜單,想著要用無限極分類,記得園子里還是什麼地方見過這種寫法,可今天找了半天也沒找到,沒辦法靜下心來自己寫了: 首先創建節點類(我給它取名:AdminUserTree): 為無限極分類填充數據,由於考慮到示來管理後臺每個頁面都會調用到,這裡我為控制器創建了一個基類方法 控制器(Co ...
今天做一個管理後臺菜單,想著要用無限極分類,記得園子里還是什麼地方見過這種寫法,可今天找了半天也沒找到,沒辦法靜下心來自己寫了:
首先創建節點類(我給它取名:AdminUserTree):
1 /// <summary> 2 /// 無限極節點類 3 /// </summary> 4 public class AdminUserTree 5 { 6 /// <summary> 7 /// 節點信息 8 /// </summary> 9 public int NodeID { get; set; } 10 /// <summary> 11 /// 節點名稱 12 /// </summary> 13 public string NodeName { get; set; } 14 /// <summary> 15 /// 父節點ID 16 /// </summary> 17 public int ParentID { get; set; } 18 /// <summary> 19 /// 對應的鏈接地址 20 /// </summary> 21 public string Url { get; set; } 22 public int? PermissionID { get; set; } 23 public int? OrderID { get; set; } 24 public string Location { get; set; } 25 public string comment { get; set; } 26 public string ImageUrl { get; set; } 27 /// <summary> 28 /// 層級 29 /// </summary> 30 public int level { get; set; } 31 /// <summary> 32 /// 子節點數目(很重要) 33 /// </summary> 34 public int childNumberl { get; set; } 35 36 /// <summary> 37 /// 子節點 (子節點是一個List)這種用法叫什麼? 38 /// </summary> 39 public List<AdminUserTree> childNode { get; set; } 40 }
為無限極分類填充數據,由於考慮到示來管理後臺每個頁面都會調用到,這裡我為控制器創建了一個基類方法
1 /// <summary> 2 /// 管理頁面基類(MVC Controller) 3 /// </summary> 4 public class AdminBase: Controller 5 { 6 /// <summary> 7 /// EF數據訪問配置 8 /// </summary> 9 private readonly ApplicationDbContext _basecontext; 10 11 /// <summary> 12 /// 管理菜單 這裡是基數,聲明為屬性以便控制器裡面可以用到 13 /// </summary> 14 public AdminUserTree leftMenu { get; set; } 15 16 17 public AdminBase(ApplicationDbContext context) 18 { 19 _basecontext = context; 20 //初始化無限極分類管理菜單 21 buildtree(); 22 } 23 24 /// <summary> 25 /// 建立無限極節點樹-管理菜單 26 /// </summary> 27 public void buildtree() 28 { 29 AdminUserTree result = new AdminUserTree(); 30 //初始化一個節點做為根節點 31 result.NodeID = 0; 32 result.NodeName= "管理員菜單"; 33 result.Url = ""; 34 result.ParentID = -1; 35 result.Location = ""; 36 result.OrderID = 0; 37 result.comment = ""; 38 result.ImageUrl = ""; 39 result.PermissionID = 0; 40 result.level = 0; 41 result.childNumberl = 0; 42 //把根節點傳遞給遞歸方法去創建子節點 43 result.childNode=BuildMenuTree(result, -1); 44 leftMenu = result; 45 } 46 47 /// <summary> 48 /// 遞歸創建子節點方法 49 /// </summary> 50 /// <param name="node">要為其分配子節點的父級節點</param> 51 /// <param name="levelID">層級關係</param> 52 /// <returns></returns> 53 protected List<AdminUserTree> BuildMenuTree(AdminUserTree node, int levelID) 54 { 55 var listtree = _basecontext.Admintree; 56 57 //從資料庫中取出node節點的全部子節點 條件:m.ParentID==node.NodeID 58 List<AdminUserTree> lt = listtree.Where(m => m.ParentID==node.NodeID ) 59 .Select(m=>new AdminUserTree() { 60 NodeID =m.NodeID 61 ,NodeName=m.NodeName 62 ,Url=m.Url 63 ,ParentID=m.ParentID 64 ,Location=m.Location 65 ,OrderID=m.OrderID 66 ,comment=m.comment 67 ,ImageUrl=m.ImageUrl 68 ,PermissionID=m.PermissionID}) 69 .ToList(); 70 71 if (lt != null) 72 { 73 //節點深度 74 node.level = levelID + 1; 75 //子節點數量,便於前端遞歸輸出時調用 76 node.childNumberl = lt.Count; 77 for (int i = 0; i < lt.Count; i++) 78 { 79 //遞歸調用創建子節點 80 lt[i].childNode = BuildMenuTree(lt[i], node.level); 81 } 82 return lt; 83 84 } 85 else { 86 return null; 87 } 88 89 } 90 91 }
控制器(Controller)繼承及調用代碼:
1 public class AdminTreeController : AdminBase 2 { 3 private readonly ApplicationDbContext _context; 4 5 6 public AdminTreeController(ApplicationDbContext context):base(context) 7 { 8 _context = context; 9 } 10 11 // GET: AdminTree 12 public async Task<IActionResult> Index(int id=1) 13 { 14 var treelist = _context.Admintree; 15 var pageOption = new WeiPagerOption 16 { 17 CurrentPage = id, 18 PageSize = 15, 19 Total = await treelist.CountAsync(), 20 RouteUrl = "/Admintree/Index", 21 pageNumStep = 5 22 }; 23 24 //分頁參數 25 ViewBag.PagerOption = pageOption; 26 27 //無限極分類菜單綁定在這裡 28 ViewBag.mainMenu = leftMenu; 29 30 //返回主要數據 31 return View(await treelist.OrderByDescending(b => b.OrderID).Skip((pageOption.CurrentPage - 1) * pageOption.PageSize).Take(pageOption.PageSize).ToListAsync()); 32 } 33 }
View層代碼:
1 @model IEnumerable<Hxwei.WebWQSF.Models.AdminTreeModel> 2 @using Hxwei.WebWQSF; 3 @using Hxwei.WebWQSF.Controllers; 4 @using System.Text; 5 @{ 6 ViewData["Title"] = "菜單管理"; 7 } 8 @functions 9 { 10 public string getAdminMenu(AdminUserTree node) 11 { 12 StringBuilder sb = new StringBuilder(); 13 14 List<AdminUserTree> ls = node.childNode; 15 if(ls.Count>0) 16 { 17 //遍歷每個子節點以輸出,這裡用到了排序ls.OrderBy(m => m.OrderID) 18 foreach (var r in ls.OrderBy(m => m.OrderID)) 19 { 20 if (r.childNumberl > 0) 21 { 22 //當存在子菜單時的方法,這裡會有遞歸調用 23 sb.Append("<div class=\"btn-group\">"); 24 sb.Append("<button type=\"button\" class=\"btn btn-default dropdown-toggle\" data-toggle=\"dropdown\">"); 25 sb.Append(r.NodeName); 26 sb.Append("<span class=\"caret\"></span>"); 27 sb.Append("</button>"); 28 sb.Append("<ul class=\"dropdown-menu\" role=\"menu\">"); 29 //遞歸調用 30 sb.Append(getAdminMenu(r)); 31 sb.Append("</ul>"); 32 sb.Append("</div>"); 33 34 } 35 else 36 { 37 //當不存在子菜單時輸出 38 string ntext = string.Format("<li><a href=\"{0}\">{1}</a></li>",r.Url,r.NodeName); 39 sb.Append(ntext); 40 41 } 42 } 43 44 } 45 46 47 return sb.ToString(); 48 } 49 } 50 <div class="row"> 51 <div class="col-md-3 navbar-inverse"> 52 <div class="btn-group-vertical col-md-10"> 53 54 <button type="button" class="btn btn-default">@ViewBag.mainMenu.NodeName</button> 55 @Html.Raw(getAdminMenu(ViewBag.mainMenu)); 56 57 </div> 58 59 </div> 60 <div class="col-md-9"> 61 <h2>Index</h2> 62 63 <p> 64 <a asp-action="Create">Create New</a> 65 </p> 66 <table class="table"> 67 <thead> 68 <tr> 69 <th> 70 @Html.DisplayNameFor(model => model.NodeName) 71 </th> 72 <th> 73 @Html.DisplayNameFor(model => model.ParentPath) 74 </th> 75 <th> 76 @Html.DisplayNameFor(model => model.OrderID) 77 </th> 78 <th> 79 @Html.DisplayNameFor(model => model.Url) 80 </th> 81 <th></th> 82 </tr> 83 </thead> 84 <tbody> 85 @foreach (var item in Model) 86 { 87 <tr> 88 <td> 89 @Html.DisplayFor(modelItem => item.NodeName) 90 </td> 91 <td> 92 @Html.DisplayFor(modelItem => item.ParentPath) 93 </td> 94 <td> 95 @Html.DisplayFor(modelItem => item.OrderID) 96 </td> 97 <td> 98 @Html.DisplayFor(modelItem => item.Url) 99 </td> 100 <td> 101 <a asp-action="Edit" asp-route-id="@item.NodeID">Edit</a> | 102 <a asp-action="Details" asp-route-id="@item.NodeID">Details</a> | 103 <a asp-action="Delete" asp-route-id="@item.NodeID">Delete</a> 104 </td> 105 </tr> 106 } 107 </tbody> 108 <tr><td colspan="5" align="center"><pager pager-option="ViewBag.PagerOption as WeiPagerOption"></pager></td></tr> 109 </table> 110 </div> 111 </div>
最後生成的菜單瀏覽器展示效果如下:
寫了很久簡單三層,最近決定用一下ASP.NET MVC,最近剛瞭解了一下ASP.NET MVC,目前最新的算是ASP.NET Core MVC,這個例子就是剛剛安裝了VS2017 RC後用ASP.NET Core MVC來實現的。學習階段希望與各位大神共勉,有不足的地方請多多指教!謝謝!
在做完這個類子後,我覺得後續還有可以優化的地方,我是從這幾個方面考慮的,希望高手給予指點:
1.這裡在構建無限極分類樹時我是多次調用資料庫查詢,如果數據量小的話想著是把數據一次取出然後傳遞後遞歸方法進行操作;由於用了EF,對於EF我也是個新手,只是剛剛會用,不知道EF本身會不會對這種類型的操作進行優化及數據緩存。
2.第二個方面是在無限極分類樹數據真充好後由於每個管理頁面都要調用這個樹的數據,考慮要對其進行緩存,如何緩存是我下一步要考慮的方法;
3.同時每個節點的許可權不同,由於每個用戶角色的不同許可權所能調用的菜單功能也不一致,這就存在了是為每一個用戶都緩存一棵樹還是全局共用一棵樹的問題,顯然前者是不科學的,應該是全員共用一棵樹的數據,只需要在View層顯示時加以許可權判斷就可以了。這也是我在下一步要考慮的。
後續會先解決以上提到第2和第3方面的問題,等我寫好後再把代碼分享出來,大家一起討論!