ASP.NET Core 一步步搭建我的網站(3)_菜單管理

上一章,咱們實現了用戶的註冊和登陸,登陸以後展現的是咱們的主頁,頁面的左側是多級的導航菜單,定位並展現用戶須要訪問的不一樣頁面。目前導航菜單是寫死的,考慮之後菜單管理的便捷性,咱們這節實現下可視化配置菜單的功能,這樣之後咱們能夠動態的配置導航菜單,不用再編譯發佈網站程序了。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 })

實現後效果:點擊菜單後,再也不折疊

小結

至此,咱們第一個後臺管理功能--菜單管理已經完成,咱們來看下效果:

相關文章
相關標籤/搜索