目前我们的MainLayout
还是默认的,这里我们需要修改为BootstrapBlazor
的Layout,并且处理一下菜单。
修改MainLayout
BootstrapBlazor
已经自带了一个Layout
组件,这个组件里常用功能已经很全了,所以我们直接使用这个组件即可。
<Layout SideWidth="0" IsPage="true" IsFullSide="true" IsFixedHeader="true" IsFixedFooter="true" ShowFooter="true" ShowCollapseBar="true" OnCollapsed="@OnCollapsed" Menus="@_menuItems"> <Header> <span class="ms-3 flex-sm-fill d-none d-sm-block">BlazorLearn</span> <div class="flex-fill d-sm-none"> </div> <Logout ImageUrl="images/argo-c.png" DisplayName="@_user.Name" UserName="@_user.UserName"> <LinkTemplate> <LogoutLink Url="/api/account/logout"></LogoutLink> </LinkTemplate> </Logout> </Header> <Side> <div class="layout-banner"> <img class="layout-logo" src="images/Argo.png" /> <div class="layout-title"> <span>BlazorLearn</span> </div> </div> </Side> <Main> <CascadingValue Value="this" IsFixed="true"> @Body </CascadingValue> </Main> <Footer> <div class="text-center flex-fill"> <a href="/" target="_blank">BlazorLearn</a> </div> </Footer> </Layout> @code { private bool IsCollapsed { get; set; } private List<MenuItem>? _menuItems; [NotNull] private UserEntity? _user; private Task OnCollapsed(bool collapsed) { IsCollapsed = collapsed; return Task.CompletedTask; } protected override void OnInitialized() { base.OnInitialized(); _user = UserEntity.Where(x => x.UserName == Furion.App.User.FindFirstValue(ClaimTypes.Name)).First(); if (_user == null) { return; } _menuItems = CreateMenuItems(MenuEntity.Where(x => x.Roles!.Any(y => y.Id == _user.RoleId)).ToList(), 0); } private List<MenuItem> CreateMenuItems(List<MenuEntity> menus, int parentId) { var selectedMenus = new List<MenuItem>(); var selectedMenuEntities = menus.Where(x => x.ParentId == parentId).ToList(); foreach (var menuEntity in selectedMenuEntities) { var menuItem = new MenuItem(menuEntity.Name!, menuEntity.Url, menuEntity.Icon); menuItem.Items = CreateMenuItems(menus, menuEntity.Id); selectedMenus.Add(menuItem); } return selectedMenus; } }
这里没什么需要多说的,每个参数的意义在文档里都比较清楚,如果需要查询具体的含义,可以看这里。
这里需要注意的是,Menus
里面必须要定义一个变量,不能直接放一个方法,否则此方法每次跳转都会执行,导致菜单不正常。
另外Logout
是一个独立的组件,这个组件其实叫Logout并不贴切,它是一个带有头像,欢迎信息以及下拉菜单的用户信息组件。
这里我们只放一个LogoutLink
登出菜单。
修改AdminHandler
如果你直接启动项目,会发现Layout
不见了,因为我们的Layout里面做了比较多的处理,会有一个需要权限验证的请求。这个请求不会携带Resource
,所以不会返回true
。就导致Layout
一直不显示,所以我们需要处理这种情况,我们目前修改为Resource
里不是RouteData
的全部都通过验证。
public override Task<bool> PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext) { if (!int.TryParse(context.User.FindFirst(ClaimTypes.Role)?.Value, out var roleId)) { return Task.FromResult(false); } if (context.Resource is RouteData routeData) { var routeAttr = routeData.PageType.CustomAttributes.FirstOrDefault(x => x.AttributeType == typeof(RouteAttribute)); if (routeAttr == null) { return Task.FromResult(true); } else { var url = routeAttr.ConstructorArguments[0].Value as string; var permission = MenuEntity .Where(x => x.Roles!.Any(y => y.Id == roleId) && x.Url == url).First(); if (permission != null) { return Task.FromResult(true); } } } else { return Task.FromResult(true); } return Task.FromResult(false); }
这样我们再启动应该就可以看到Layout
了。
修改LoginController
我们之前只有一个登录,所以我们写了一个LoginController
,现在我们需要加入登出,所以我们直接把LoginController
改为AccountController
,然后内容改为PostLogin
、GetLogout
。
public class AccountController: IDynamicApiController { public async Task<object> PostLogin([FromBody]LoginVo loginVo) { if (string.IsNullOrEmpty(loginVo.UserName)) { return new { code = 50000, message = "用户名不能为空" }; } if (string.IsNullOrEmpty(loginVo.Password)) { return new { code = 50000, message = "密码不能为空" }; } var password = MD5Encryption.Encrypt(loginVo.Password); var user = await UserEntity.Where(x => x.UserName == loginVo.UserName && x.Password == password).Include(x => x.Role).FirstAsync(); if (user != null) { var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName!)); identity.AddClaim(new Claim(ClaimTypes.Role, user.Role!.Id.ToString())); await Furion.App.HttpContext.SignInAsync(new ClaimsPrincipal(identity), new AuthenticationProperties(){IsPersistent = true, ExpiresUtc = loginVo.RememberMe? DateTimeOffset.Now.AddDays(5): DateTimeOffset.Now.AddMinutes(30)}); return new { code = 20000, message = "登录成功" }; } return new { code = 50000, message = "用户名或密码错误" }; } [Authorize] public async Task<IActionResult> GetLogout() { await Furion.App.HttpContext.SignOutAsync(); return new RedirectResult("/Login"); } }
这里我们直接给Logout
加[Authorize]
,只有登录以后才能访问。
代码在github:https://github.com/j4587698/BlazorLearnj,分支lesson9。