上一章,咱們實現了用戶的註冊和登陸,登陸以後展現的是咱們的主頁,頁面的左側是多級的導航菜單,定位並展現用戶須要訪問的不一樣頁面。目前導航菜單是寫死的,考慮之後菜單管理的便捷性,咱們這節實現下可視化配置菜單的功能,這樣之後咱們能夠動態的配置導航菜單,不用再編譯發佈網站程序了。css
第1步,左側導航菜單中,添加後臺管理模塊,用做管理員登陸後,能夠進行一些後臺管理的操做,固然,目前尚未權限控制(後期加入),因此對全部用戶可見。大概菜單結構以下:html
有了菜單項,咱們還須要控制視圖的跳轉,因此,接下來須要寫對應的控制器和視圖。前端
爲了將相關功能組織成一組單獨命名空間(路由)和文件夾結構(視圖),解決方案中右鍵添加區域(Area),取名後臺管理(Configuration),表明後臺管理模塊,.Net Core腳手架(scaffold)自動幫咱們實現了目錄劃分:控制器(Controllers)、模型(Models)、視圖(Views)ajax
菜單的基本屬性有:菜單名稱、菜單類型、菜單的圖標樣式、菜單url路徑。另外,菜單在邏輯上是樹狀結構,可是要在物理數據庫中存儲,須要進行扁平化處理,每一個菜單項有個父菜單屬性(根節點的父菜單爲空),還有同一父節點底下,在組類的排序屬性,定義以下:數據庫
1 /// <summary> 2 /// 菜單 3 /// </summary> 4 public class Menu 5 { 6 /// <summary> 7 /// 主鍵ID 8 /// </summary> 9 [DatabaseGenerated(DatabaseGeneratedOption.None)] 10 [Required(ErrorMessage = "請輸入菜單編號")] 11 public string Id { get; set; } 12 13 /// <summary> 14 /// 菜單名稱 15 /// </summary> 16 [Required(ErrorMessage = "請輸入菜單名稱")] 17 [StringLength(256)] 18 public string Name { get; set; } 19 20 /// <summary> 21 /// 父級ID 22 /// </summary> 23 [DisplayFormat(NullDisplayText = "無")] 24 public string ParentId { get; set; } 25 26 /// <summary> 27 /// 菜單組內排序 28 /// </summary> 29 [Range(0, 99, ErrorMessage = "請選擇1-99範圍內的整數")] 30 public int IndexCode { get; set; } 31 32 /// <summary> 33 /// 菜單路徑 34 /// </summary> 35 [StringLength(256)] 36 [DisplayFormat(NullDisplayText = "無")] 37 public string Url { get; set; } 38 39 /// <summary> 40 /// 類型:0導航菜單;1操做按鈕。 41 /// </summary> 42 [Required(ErrorMessage = "請選擇菜單類型")] 43 public MenuTypes? MenuType { get; set; } 44 45 /// <summary> 46 /// 菜單圖標名稱 47 /// </summary> 48 [Required(ErrorMessage = "請輸入菜單圖標")] 49 [StringLength(50)] 50 public string Icon { get; set; } 51 52 /// <summary> 53 /// 菜單備註 54 /// </summary> 55 public string Remarks { get; set; } 56 } 57 /// <summary> 58 /// 菜單類型 59 /// </summary> 60 public enum MenuTypes 61 { 62 /// <summary> 63 /// 導航菜單 64 /// </summary> 65 導航菜單, 66 /// <summary> 67 /// 操做菜單 68 /// </summary> 69 操做菜單 70 }
有了咱們的菜單模型,在控制器目錄中,咱們右鍵創建第1個本身的控制器,取名MenuController,用來菜單管理,上下文選取定義好的Menu模型,仍是利用腳手架,自動幫咱們生成增刪改查對應的後來邏輯和視圖。此時,咱們把菜單導向該控制器,實際上是能夠正常訪問的,不過還遠遠達不到咱們的要求,因此咱們還得完善下自動生成的代碼。cookie
爲了方便從此的拓展,新增一個AppController控制器,繼承Controller,之後全部的控制器,都繼承於AppController,方便一些公共的方法調用。數據結構
.Net Core有個比較方便的一點,就是實現了構造器的依賴注入,這樣咱們不用像之前那樣手工New一個DBContext對象,直接在控制器將須要的DBContext注入,調用的時候,直接訪問注入的對象便可,有關依賴注入的知識,這裏就不在多說了,有興趣你們能夠了解一下:.Net Core依賴注入async
首先,在ApplicationDbContext添加Menu數據集ide
1 public class ApplicationDbContext : IdentityDbContext<ApplicationUser> 2 { 3 public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) 4 : base(options) 5 { 6 } 7 8 protected override void OnModelCreating(ModelBuilder builder) 9 { 10 base.OnModelCreating(builder); 11 } 12 13 public DbSet<ApplicationUser> ApplicationUsers { get; set; } 14 15 public DbSet<Menu> Menus { get; set; } 16 }
這裏咱們修改下MenuController構造器:post
1 private readonly ApplicationDbContext _context; 2 3 public MenuController(ApplicationDbContext context, INavMenuService navMenuService) 4 { 5 _context = context; 6 _NavMenuService = navMenuService; 7 }
爲了後面方便統一提供下拉框選擇,這裏實現一個下拉框初始化方法:
1 /// <summary> 2 /// 初始化下拉選擇框 3 /// </summary> 4 /// <param name="menu"></param> 5 private void UpdateDropDownList(Menu menu = null) 6 { 7 var menusParent = _context.Menus.AsNoTracking().Where(s => s.MenuType == MenuTypes.導航菜單); 8 List<SelectListItem> listMenusParent = new List<SelectListItem>(); 9 foreach (var menuParent in menusParent) 10 { 11 listMenusParent.Add(new SelectListItem 12 { 13 Value = menuParent.Id, 14 Text = menuParent.Id + $"({menuParent.Name})", 15 Selected = (menu != null && menuParent.Id == menu.ParentId) 16 }); 17 } 18 ViewBag.ParentIds = listMenusParent; 19 20 if (menu == null) 21 { 22 ViewBag.MenuTypes = MenuTypes.導航菜單.GetSelectListByEnum(); 23 } 24 else 25 { 26 ViewBag.MenuTypes = MenuTypes.導航菜單.GetSelectListByEnum(Convert.ToInt32(menu.MenuType)); 27 } 28 }
控制器調整:增長查詢傳入參數,根據參數篩選查詢結果;
1 /// <summary> 2 /// 列表頁 3 /// </summary> 4 /// <param name="query"></param> 5 /// <returns></returns> 6 public async Task<IActionResult> Index(MenuIndexQuery query) 7 { 8 var menus = _context.Menus.AsNoTracking(); 9 if (!string.IsNullOrEmpty(query.QName)) 10 { 11 menus = menus.Where(s => s.Name.Contains(query.QName.Trim())); 12 } 13 if (!string.IsNullOrEmpty(query.QId)) 14 { 15 menus = menus.Where(s => s.Id.Contains(query.QId.Trim())); 16 } 17 if (!string.IsNullOrEmpty(query.QParentId)) 18 { 19 menus = menus.Where(s => s.ParentId == query.QParentId.Trim()); 20 } 21 if (query.QMenuType != null) 22 { 23 menus = menus.Where(s => s.MenuType == query.QMenuType); 24 } 25 26 UpdateDropDownList(); 27 return View(new MenuIndexVM { Menus = await menus.ToListAsync(), Query = query }); 28 }
視圖調整:用戶點擊刪除時,彈出確認框,調用Ajax方式刪除數據,再也不經過頁面跳轉;
1 @using MyWebSite.ViewModels 2 @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 4 @model MyWebSite.Areas.Configuration.ViewModels.MenuIndexVM 5 @{ 6 ViewData["Title"] = "菜單列表"; 7 8 var breadcrumb = new BreadCrumb("菜單列表", "Version 2.0", new List<NavCrumb> 9 { 10 new NavCrumb(name:"菜單管理",url: "/Configuration/Menu"), 11 new NavCrumb(name:"菜單列表"), 12 }); 13 ViewBag.BreadCrumb = breadcrumb; 14 15 Layout = "~/Views/Shared/_Layout.cshtml"; 16 } 17 18 <div class="row"> 19 <div class="col-xs-12"> 20 <div class="box with-border"> 21 <form class="form" asp-action="Index"> 22 <div class="box-header"> 23 <h3 class="box-title"><i class="fa fa-search margin-r-5">查詢條件</i></h3> 24 <div class="box-tools pull-right"> 25 <button type="submit" class="btn btn-success margin-r-5"><i class="fa fa-search margin-r-5"></i>查詢</button> 26 <a class="btn btn-primary" href="/Configuration/Menu/Create"><i class="fa fa-plus margin-r-5"></i>新建</a> 27 </div> 28 <div asp-validation-summary="All" class="text-danger"></div> 29 <div class="row"> 30 <div class="col-md-3"> 31 <div class="form-group"> 32 <label asp-for="Query.QName">菜單名稱:</label> 33 <input asp-for="Query.QName" class="form-control input-sm"> 34 </div> 35 </div> 36 <div class="col-md-3"> 37 <div class="form-group"> 38 <label asp-for="Query.QId">菜單編碼:</label> 39 <input asp-for="Query.QId" class="form-control input-sm"> 40 </div> 41 </div> 42 <div class="col-md-3"> 43 <div class="form-group"> 44 <label asp-for="Query.QParentId">父級菜單:</label> 45 <select asp-for="Query.QParentId" class="form-control input-sm select2" asp-items="ViewBag.ParentIds"> 46 <option value="">-- 請選擇 --</option> 47 </select> 48 </div> 49 </div> 50 <div class="col-md-3"> 51 <div class="form-group"> 52 <label asp-for="Query.QMenuType">菜單類型:</label> 53 <select asp-for="Query.QMenuType" class="form-control input-sm select2" asp-items="ViewBag.MenuTypes"> 54 <option value="">-- 請選擇 --</option> 55 </select> 56 </div> 57 </div> 58 </div> 59 </div> 60 </form> 61 <div class="box-body"> 62 <table class="table table-bordered table-hover" style="width: 100%"> 63 <thead> 64 <tr> 65 <th>#</th> 66 <th>菜單名稱</th> 67 <th>菜單編號</th> 68 <th>父級編號</th> 69 <th>組內排序</th> 70 <th>菜單類型</th> 71 <th>菜單圖標</th> 72 <th>菜單路徑</th> 73 <th>操做</th> 74 </tr> 75 </thead> 76 <tbody> 77 @{ 78 var index = 0; 79 } 80 @foreach (var item in Model.Menus) 81 { 82 index++; 83 <tr> 84 <td> 85 @index.ToString("D3") 86 </td> 87 <td> 88 @Html.ActionLink(@item.Name, "Details", new { id = @item.Id }) 89 </td> 90 <td> 91 <span>@item.Id</span> 92 </td> 93 <td> 94 <span>@Html.DisplayFor(modelItem => item.ParentId)</span> 95 </td> 96 <td> 97 <span>@item.IndexCode</span> 98 </td> 99 <td> 100 <span>@item.MenuType</span> 101 </td> 102 <td> 103 <i class="fa @item.Icon" data-toggle="tooltip" data-placement="right" title="@item.Icon"></i> 104 </td> 105 <td> 106 <i class="fa fa-ellipsis-h" data-toggle="tooltip" data-placement="top" title="@Html.DisplayFor(modelItem => item.Url)"></i> 107 </td> 108 <td> 109 @Html.ActionLink("編輯", "Edit", new { id = @item.Id })| 110 @Html.ActionLink("詳情", "Details", new { id = @item.Id })| 111 <a href="#" onclick="onDelete('@item.Id', '@item.Name');">刪除</a> 112 </td> 113 </tr> 114 } 115 </tbody> 116 </table> 117 </div> 118 </div> 119 </div> 120 </div> 121 122 @section Scripts{ 123 @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} 124 <script> 125 function onDelete(id, name) { 126 BootstrapDialog.show({ 127 message: '確認刪除菜單-' + name + '[' + id + ']?', 128 size: BootstrapDialog.SIZE_SMALL, 129 draggable: true, 130 buttons: [ 131 { 132 icon: 'fa fa-check', 133 label: '肯定', 134 cssClass: 'btn-primary', 135 action: function (dialogRef) { 136 dialogRef.close(); 137 $.ajax({ 138 type: 'POST', 139 url: '/Configuration/Menu/Delete', 140 data: { id: id }, 141 success: function () { 142 location.reload(); 143 } 144 }); 145 } 146 }, { 147 icon: 'fa fa-close', 148 label: '取消', 149 action: function (dialogRef) { 150 dialogRef.close(); 151 } 152 } 153 ] 154 }); 155 } 156 </script> 157 }
控制器調整:這裏控制器有2個Create方法,一個是Http Get類型,用戶列表頁點新建時,跳轉到該方法,另一個是Http Post類型,用戶填完新建的菜單信息後,點擊保存,跳轉到該方法。在Http Post方法中,爲了防止頁面over post,須要指定綁定的屬性Bind("Id,Name,ParentId,IndexCode,Url,MenuType,Icon,Remarks"),固然,也能夠用TryUpdateModel()實現,之後再介紹;
1 /// <summary> 2 /// 新建空白頁面 3 /// </summary> 4 /// <returns></returns> 5 public IActionResult Create() 6 { 7 var model = new Menu 8 { 9 Id = "MXX_XX_XX", 10 IndexCode = 1, 11 Icon = "fa-circle-o" 12 }; 13 UpdateDropDownList(); 14 return View(model); 15 } 16 17 /// <summary> 18 /// 新建保存頁面 19 /// </summary> 20 /// <param name="menu"></param> 21 /// <returns></returns> 22 [HttpPost] 23 public async Task<IActionResult> Create([Bind("Id,Name,ParentId,IndexCode,Url,MenuType,Icon,Remarks")] Menu menu) 24 { 25 if (ModelState.IsValid) 26 { 27 if (!MenuExists(menu.Id)) 28 { 29 _context.Add(menu); 30 await _context.SaveChangesAsync(); 31 32 _NavMenuService.InitOrUpdate(); 33 return RedirectToAction(nameof(Index)); 34 } 35 else 36 { 37 ModelState.AddModelError("Id", "菜單編號已存在,請修改菜單編號."); 38 } 39 } 40 UpdateDropDownList(menu); 41 return View(menu); 42 }
視圖調整: 引入前端數據驗證,並增長一些數據控制,好比菜單類型非操做菜單時,菜單路徑不可編輯等等;
1 @using MyWebSite.ViewModels 2 @using MyWebSite.Areas.Configuration.Models 3 @model MyWebSite.Areas.Configuration.Models.Menu 4 5 @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 6 7 @{ 8 ViewData["Title"] = "菜單新建"; 9 10 var breadcrumb = new BreadCrumb("菜單新建", "Version 2.0", new List<NavCrumb> 11 { 12 new NavCrumb(name:"菜單管理",url: "/Configuration/Menu"), 13 new NavCrumb(name:"菜單新建"), 14 }); 15 ViewBag.BreadCrumb = breadcrumb; 16 17 Layout = "~/Views/Shared/_Layout.cshtml"; 18 } 19 <section class="content"> 20 <div class="row"> 21 <div class="col-md-8"> 22 <div class="box"> 23 <div class="box-header with-border"> 24 <h3 class="box-title">新建</h3> 25 </div> 26 <form asp-action="Create"> 27 <div asp-validation-summary="All" class="text-danger"></div> 28 <div class="box-body"> 29 <div class="form-group col-md-6"> 30 <label asp-for="Id">菜單編號</label> 31 <input asp-for="Id" class="form-control input-sm"> 32 </div> 33 <div class="form-group col-md-6"> 34 <label asp-for="Name">菜單名稱</label> 35 <input asp-for="Name" class="form-control input-sm"> 36 </div> 37 <div class="form-group col-md-6"> 38 <label asp-for="ParentId">父級菜單</label> 39 <select asp-for="ParentId" class="form-control input-sm select2" asp-items="ViewBag.ParentIds"> 40 <option value="">-- 請選擇 --</option> 41 </select> 42 </div> 43 <div class="form-group col-md-6"> 44 <label asp-for="IndexCode">組內排序</label> 45 <input asp-for="IndexCode" class="form-control input-sm"> 46 </div> 47 <div class="form-group col-md-6"> 48 <label asp-for="MenuType">菜單類型</label> 49 <select asp-for="MenuType" class="form-control input-sm" asp-items="ViewBag.MenuTypes"> 50 <option value="">-- 請選擇 --</option> 51 </select> 52 </div> 53 <div class="form-group col-md-6"> 54 <label asp-for="Icon">菜單圖標</label> 55 <div class="input-group"> 56 <span class="input-group-addon"><i id="IconfShow" class="fa @Model.Icon"></i></span> 57 <input asp-for="Icon" class="form-control input-sm"> 58 </div> 59 </div> 60 <div class="form-group col-md-6"> 61 <label asp-for="Url">菜單路徑</label> 62 @if (Model.MenuType == MenuTypes.操做菜單) 63 { 64 <input asp-for="Url" class="form-control input-sm"> 65 } 66 else 67 { 68 <input asp-for="Url" class="form-control input-sm" readonly> 69 } 70 </div> 71 <div class="form-group col-md-6"> 72 <label asp-for="Remarks">備註</label> 73 <input asp-for="Remarks" class="form-control input-sm"> 74 </div> 75 </div> 76 <div class="box-footer"> 77 <button type="submit" class="btn btn-primary"><i id="IconfShow" class="fa fa-save"></i> 保存</button> 78 <a asp-action="Index" class="btn btn-default"><i id="IconfShow" class="fa fa-undo"></i> 返回</a> 79 </div> 80 </form> 81 </div> 82 83 </div> 84 </div> 85 </section> 86 @section Scripts { 87 <script src="~/js/Configuration/Menu.js"></script> 88 @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} 89 }
控制器調整:不用大的調整,只是增長了下拉框的初始化工做 ;
1 /// <summary> 2 /// 詳情頁 3 /// </summary> 4 /// <param name="id"></param> 5 /// <returns></returns> 6 public async Task<IActionResult> Details(string id) 7 { 8 if (id == null) 9 { 10 return NotFound(); 11 } 12 13 var menu = await _context.Menus 14 .SingleOrDefaultAsync(m => m.Id == id); 15 if (menu == null) 16 { 17 return NotFound(); 18 } 19 20 UpdateDropDownList(menu); 21 return View(menu); 22 }
視圖調整:跟建立界面大致差很少, 只是控制屬性字段不容許編輯,也不用數據驗證;
@using MyWebSite.ViewModels @model MyWebSite.Areas.Configuration.Models.Menu @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @{ ViewData["Title"] = "菜單詳情"; var breadcrumb = new BreadCrumb("菜單詳情", "Version 2.0", new List<NavCrumb> { new NavCrumb(name:"菜單管理",url: "/Configuration/Menu"), new NavCrumb(name:"菜單詳情"), new NavCrumb(name:Model.Id), }); ViewBag.BreadCrumb = breadcrumb; Layout = "~/Views/Shared/_Layout.cshtml"; } <section class="content"> <div class="row"> <div class="col-md-8"> <div class="box"> <div class="box-header with-border"> <h3 class="box-title">詳情</h3> </div> <form> <div class="box-body"> <div class="form-group col-md-6"> <label asp-for="Id">菜單編號</label> <input asp-for="Id" class="form-control input-sm" readonly> </div> <div class="form-group col-md-6"> <label asp-for="Name">菜單名稱</label> <input asp-for="Name" class="form-control input-sm" readonly> </div> <div class="form-group col-md-6"> <label asp-for="ParentId">父級菜單</label> <select asp-for="ParentId" class="form-control input-sm" asp-items="ViewBag.ParentIds" disabled> </select> </div> <div class="form-group col-md-6"> <label asp-for="IndexCode">組內排序</label> <input asp-for="IndexCode" class="form-control input-sm" readonly> </div> <div class="form-group col-md-6"> <label asp-for="MenuType">菜單類型</label> <input asp-for="MenuType" class="form-control input-sm" readonly> </div> <div class="form-group col-md-6"> <label asp-for="Icon">菜單圖標</label> <div class="input-group"> <span class="input-group-addon"><i id="IconfShow" class="fa @Model.Icon"></i></span> <input asp-for="Icon" class="form-control input-sm" readonly> </div> </div> <div class="form-group col-md-6"> <label asp-for="Url">菜單路徑</label> <input asp-for="Url" class="form-control input-sm" readonly> </div> <div class="form-group col-md-6"> <label asp-for="Remarks">備註</label> <input asp-for="Remarks" class="form-control input-sm" readonly> </div> </div> <div class="box-footer"> <a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-primary"><i id="IconfShow" class="fa fa-edit"></i> 編輯</a> <a asp-action="Index" class="btn btn-default"><i id="IconfShow" class="fa fa-undo"></i> 返回</a> </div> </form> </div> </div> </div> </section>
控制器調整:也是有Http Get和Http Post方法,分別是開始編輯和編輯保存跳轉的方法,同時加上防止over post字段綁定;
1 /// <summary> 2 /// 開始編輯 3 /// </summary> 4 /// <param name="id"></param> 5 /// <returns></returns> 6 public async Task<IActionResult> Edit(string id) 7 { 8 if (id == null) 9 { 10 return NotFound(); 11 } 12 13 var menu = await _context.Menus.SingleOrDefaultAsync(m => m.Id == id); 14 if (menu == null) 15 { 16 return NotFound(); 17 } 18 19 UpdateDropDownList(menu); 20 return View(menu); 21 } 22 23 /// <summary> 24 /// 編輯保存 25 /// </summary> 26 /// <param name="id"></param> 27 /// <param name="menu"></param> 28 /// <returns></returns> 29 [HttpPost] 30 public async Task<IActionResult> Edit(string id, [Bind("Id,Name,ParentId,IndexCode,Url,MenuType,Icon,Remarks")] Menu menu) 31 { 32 if (id != menu.Id) 33 { 34 return NotFound(); 35 } 36 37 if (ModelState.IsValid) 38 { 39 try 40 { 41 _context.Update(menu); 42 await _context.SaveChangesAsync(); 43 } 44 catch (DbUpdateConcurrencyException) 45 { 46 if (!MenuExists(menu.Id)) 47 { 48 return NotFound(); 49 } 50 else 51 { 52 throw; 53 } 54 } 55 _NavMenuService.InitOrUpdate(); 56 return RedirectToAction(nameof(Index)); 57 } 58 59 UpdateDropDownList(menu); 60 return View(menu); 61 }
視圖調整:跟建立界面大致差很少,須要數據驗證和數據控制;
1 @using MyWebSite.ViewModels 2 @using MyWebSite.Areas.Configuration.Models 3 @model MyWebSite.Areas.Configuration.Models.Menu 4 5 @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 6 7 @{ 8 ViewData["Title"] = "菜單編輯"; 9 10 var breadcrumb = new BreadCrumb("菜單編輯", "Version 2.0", new List<NavCrumb> 11 { 12 new NavCrumb(name:"菜單管理",url: "/Configuration/Menu"), 13 new NavCrumb(name:"菜單編輯"), 14 new NavCrumb(name:Model.Id), 15 }); 16 ViewBag.BreadCrumb = breadcrumb; 17 18 Layout = "~/Views/Shared/_Layout.cshtml"; 19 } 20 <section class="content"> 21 <div class="row"> 22 <div class="col-md-8"> 23 <div class="box"> 24 <div class="box-header with-border"> 25 <h3 class="box-title">編輯</h3> 26 </div> 27 <form asp-action="Edit"> 28 <div asp-validation-summary="All" class="text-danger"></div> 29 <div class="box-body"> 30 <div class="form-group col-md-6"> 31 <label asp-for="Id">菜單編號</label> 32 <input asp-for="Id" class="form-control input-sm" readonly> 33 </div> 34 <div class="form-group col-md-6"> 35 <label asp-for="Name">菜單名稱</label> 36 <input asp-for="Name" class="form-control input-sm"> 37 </div> 38 <div class="form-group col-md-6"> 39 <label asp-for="ParentId">父級菜單</label> 40 <select asp-for="ParentId" class="form-control input-sm select2" asp-items="ViewBag.ParentIds"> 41 <option value="">-- 請選擇 --</option> 42 </select> 43 </div> 44 <div class="form-group col-md-6"> 45 <label asp-for="IndexCode">組內排序</label> 46 <input asp-for="IndexCode" class="form-control input-sm"> 47 </div> 48 <div class="form-group col-md-6"> 49 <label asp-for="MenuType">菜單類型</label> 50 <select asp-for="MenuType" class="form-control input-sm" asp-items="ViewBag.MenuTypes"> 51 <option value="">-- 請選擇 --</option> 52 </select> 53 </div> 54 <div class="form-group col-md-6"> 55 <label asp-for="Icon">菜單圖標</label> 56 <div class="input-group"> 57 <span class="input-group-addon"><i id="IconfShow" class="fa @Model.Icon"></i></span> 58 <input asp-for="Icon" class="form-control input-sm"> 59 </div> 60 </div> 61 <div class="form-group col-md-6"> 62 <label asp-for="Url">菜單路徑</label> 63 @if (Model.MenuType == MenuTypes.操做菜單) 64 { 65 <input asp-for="Url" class="form-control input-sm"> 66 } 67 else 68 { 69 <input asp-for="Url" class="form-control input-sm" readonly> 70 } 71 </div> 72 <div class="form-group col-md-6"> 73 <label asp-for="Remarks">備註</label> 74 <input asp-for="Remarks" class="form-control input-sm"> 75 </div> 76 </div> 77 <div class="box-footer"> 78 <button type="submit" class="btn btn-primary"><i id="IconfShow" class="fa fa-save"></i> 保存</button> 79 <a asp-action="Index" class="btn btn-default"><i id="IconfShow" class="fa fa-undo"></i> 返回</a> 80 </div> 81 </form> 82 </div> 83 84 </div> 85 </div> 86 </section> 87 @section Scripts { 88 <script src="~/js/Configuration/Menu.js" ></script> 89 @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} 90 }
以上,咱們能夠經過增刪改查界面操做菜單項了,可是要怎麼將數據庫中的菜單跟左側的導航菜單關聯呢?下節,咱們將實現下這個功能。
前面章節說過,物理數據庫菜單Menu是表格結構,而界面上的導航菜單是樹狀結構,那應該怎麼處理呢?咱們考慮先定義樹狀導航菜單的數據結構,而後表格結構的菜單項經過邏輯處理,轉換成樹狀的導航菜單,就能夠知足咱們的要求了。
第一步,定義導航菜單:屬性跟菜單項差很少,不一樣的是沒有父菜單,而是子菜單列表,這樣能夠包含子菜單,實現菜單的樹狀嵌套;
1 /// <summary> 2 /// 導航菜單項 3 /// </summary> 4 public class NavMenu 5 { 6 public string Id { get; set; } 7 public string Name { get; set; } 8 public MenuTypes MenuType { get; set; } 9 public string Url { get; set; } 10 public string Icon { get; set; } 11 public bool IsOpen { get; set; } 12 13 /// <summary> 14 /// 子菜單 15 /// </summary> 16 public IList<NavMenu> SubNavMenus = new List<NavMenu>(); 17 } 18 19 /// <summary> 20 /// 左側導航菜單視圖模型 21 /// </summary> 22 public class NavMenuVM 23 { 24 public IList<NavMenu> NavMenus { get; set; } 25 26 public string[] MenuidsOpen { get; set; } 27 }
第二步,實現獲取數據庫保存的全部菜單項信息服務NavMenuService:將表格結構的菜單項,轉換成樹狀的導航菜單;
1 /// <summary> 2 /// 菜單服務 3 /// </summary> 4 public class NavMenuService : INavMenuService 5 { 6 private readonly ApplicationDbContext _context; 7 public NavMenuService(ApplicationDbContext context) 8 { 9 _context = context; 10 } 11 12 private static IList<NavMenu> NavMenus { get; set; } 13 14 /// <summary> 15 /// 獲取導航菜單 16 /// </summary> 17 /// <returns></returns> 18 public IList<NavMenu> GetNavMenus() 19 { 20 if (NavMenus == null) 21 InitOrUpdate(); 22 23 return NavMenus; 24 } 25 /// <summary> 26 /// 生成導航菜單 27 /// </summary> 28 /// <returns></returns> 29 public void InitOrUpdate() 30 { 31 NavMenus = new List<NavMenu>(); 32 33 var rootMenus = _context.Menus 34 .Where(s => string.IsNullOrEmpty(s.ParentId)) 35 .AsNoTracking() 36 .OrderBy(s => s.IndexCode) 37 .ToList(); 38 39 foreach (var rootMenu in rootMenus) 40 { 41 NavMenus.Add(GetOneNavMenu(rootMenu)); 42 } 43 } 44 /// <summary> 45 /// 根據給定的Menu,生成對應的導航菜單 46 /// </summary> 47 /// <param name="menu"></param> 48 /// <returns></returns> 49 public NavMenu GetOneNavMenu(Menu menu) 50 { 51 //構建菜單項 52 var navMenu = new NavMenu 53 { 54 Id = menu.Id, 55 Name = menu.Name, 56 MenuType = menu.MenuType.Value, 57 Url = menu.Url, 58 Icon = menu.Icon 59 }; 60 61 //構建子菜單 62 var subMenus = _context.Menus 63 .Where(s => s.ParentId == menu.Id) 64 .AsNoTracking() 65 .OrderBy(s => s.IndexCode) 66 .ToList(); 67 68 foreach (var subMenu in subMenus) 69 { 70 navMenu.SubNavMenus.Add(GetOneNavMenu(subMenu)); 71 } 72 73 return navMenu; 74 }
第三步,咱們須要定義一個部分視圖_NavMenu,具體規定菜單的顯示樣式,重要的是,若是包含子菜單的時候,子菜單仍然使用_NavMenu遞歸渲染顯示,這樣理論上能夠支持無窮級別的導航菜單的顯示。若是菜單是導航菜單,增長展開樣式,並渲染子菜單,若是是操做菜單,定義href爲菜單路徑;
1 @using MyWebSite.Areas.Configuration.Models 2 @using MyWebSite.Areas.Configuration.ViewModels 3 @model MyWebSite.Areas.Configuration.ViewModels.NavMenuVM 4 5 6 @foreach (var navMenu in Model.NavMenus) 7 { 8 if (navMenu.MenuType == MenuTypes.導航菜單) 9 { 10 <li menuid="@navMenu.Id" class="treeview @(Model.MenuidsOpen.Contains(navMenu.Id) ? "menu-open" : "")"> 11 <a href="#"> 12 <i class="fa @navMenu.Icon"></i> <span>@navMenu.Name</span> 13 <span class="pull-right-container"> 14 <i class="fa fa-angle-left pull-right"></i> 15 </span> 16 </a> 17 <ul class="treeview-menu" @(Model.MenuidsOpen.Contains(navMenu.Id) ? @"style=display:block;" : "")> 18 @await Html.PartialAsync("_NavMenu", new NavMenuVM 19 { 20 NavMenus = navMenu.SubNavMenus, 21 MenuidsOpen = Model.MenuidsOpen 22 }) 23 </ul> 24 </li> 25 } 26 else if ((navMenu.MenuType == MenuTypes.操做菜單)) 27 { 28 <li menuid="@navMenu.Id"> 29 <a href="@navMenu.Url" @(navMenu.Url != null && navMenu.Url.StartsWith("http") ? @"target=_blank" : "")> 30 <i class="fa @navMenu.Icon"></i><span>@navMenu.Name</span> 31 </a> 32 </li> 33 } 34 }
最後,咱們渲染下整個導航視圖,咱們已經有了NavMenuService服務,那怎麼在UI界面去訪問和使用它呢?其實.Net Core裏提供了很方便的機制去訪問,直接在Razor視圖裏將服務註冊就好了,如:@inject INavMenuService NavMenuServiceIns
1 @using Microsoft.AspNetCore.Http 2 @using MyWebSite.Areas.Configuration.ViewModels 3 @using MyWebSite.Services.Interfaces 4 @model MyWebSite.Models.ApplicationUser 5 6 @inject IHttpContextAccessor HttpContextAccessorIns 7 @inject INavMenuService NavMenuServiceIns 8 9 <aside class="main-sidebar"> 10 <section class="sidebar"> 11 <div class="user-panel"> 12 <div class="pull-left image"> 13 <img src="~/lib/AdminLTE/dist/img/user2-160x160.jpg" class="img-circle" alt="User Image"> 14 </div> 15 <div class="pull-left info"> 16 <p>@Model.NickName</p> 17 <a href="#"><i class="fa fa-circle text-success"></i> 在線</a> 18 </div> 19 </div> 20 <form action="#" method="get" class="sidebar-form"> 21 <div class="input-group"> 22 <input type="text" name="q" class="form-control" placeholder="Search..."> 23 <span class="input-group-btn"> 24 <button type="submit" name="search" id="search-btn" class="btn btn-flat"> 25 <i class="fa fa-search"></i> 26 </button> 27 </span> 28 </div> 29 </form> 30 <ul class="sidebar-menu" data-widget="tree"> 31 <li class="header">菜單導航</li> 32 @{ 33 var navMenus = NavMenuServiceIns.GetNavMenus(); 34 var cookieMenuidsOpen = HttpContextAccessorIns.HttpContext.Request.Cookies["menuids_open"] ?? ""; 35 } 36 @await Html.PartialAsync("_NavMenu", new NavMenuVM 37 { 38 NavMenus = navMenus, 39 MenuidsOpen = cookieMenuidsOpen == null ? new string[] { } : cookieMenuidsOpen.Split(",") 40 }) 41 42 <li><a href="https://adminlte.io/docs"><i class="fa fa-book"></i> <span>Documentation</span></a></li> 43 <li class="header">LABELS</li> 44 <li><a href="#"><i class="fa fa-circle-o text-red"></i> <span>Important</span></a></li> 45 <li><a href="#"><i class="fa fa-circle-o text-yellow"></i> <span>Warning</span></a></li> 46 <li><a href="#"><i class="fa fa-circle-o text-aqua"></i> <span>Information</span></a></li> 47 </ul> 48 </section> 49 </aside>
如今咱們的導航菜單的展現功能基本完成了,可是這裏有個小小的用戶體驗的問題,就是每次點擊導航菜單項時,因爲頁面跳轉,致使整個Layout頁面會刷新,那左側的導航菜單也會刷新,這樣以前展開的菜單就會摺疊起來:
要保持原有的菜單不被摺疊,有不少方法,好比不使用Layout,點擊導航菜單項時,經過Ajax局部刷新右側內容區域,或者直接作成單頁模式的網站,保證左側的導航菜單不因不一樣內容而刷新。這裏考慮.Net Core使用Layout的便捷性,思路以下:點擊導航菜單項時,保存展開的菜單項id到cookie中,跳轉下一個界面之後,根據cookie中的菜單項id,從新設置展開狀態
1 $('.main-sidebar a').click(function () { 2 //記錄菜單展開狀態 3 var href = $(this).attr('href') 4 if (href === null || href === "#") return 5 var menuids = []; 6 $('.menu-open').each(function () { 7 menuids.push($(this).attr('menuid')) 8 }) 9 $.cookie('menuids_open', menuids.join(','), { path: "/" }) 10 })
實現後效果:點擊菜單後,再也不折疊
至此,咱們第一個後臺管理功能--菜單管理已經完成,咱們來看下效果: