├── .gitignore ├── BlazorLearn.sln ├── BlazorLearn ├── AdminHandler.cs ├── App.razor ├── BlazorLearn.csproj ├── Controller │ └── AccountController.cs ├── Data │ ├── ChangePassword.cs │ ├── WeatherForecast.cs │ └── WeatherForecastService.cs ├── Entity │ ├── MenuEntity.cs │ ├── RoleEntity.cs │ ├── RoleMenuEntity.cs │ └── UserEntity.cs ├── Extensions │ └── DbExtension.cs ├── Pages │ ├── Account │ │ ├── Login.razor │ │ ├── Login.razor.cs │ │ └── Login.razor.css │ ├── Admin │ │ ├── Counter.razor │ │ ├── Error.cshtml │ │ ├── Error.cshtml.cs │ │ ├── FetchData.razor │ │ ├── Index.razor │ │ ├── Menu.razor │ │ ├── Role.razor │ │ ├── User.razor │ │ └── _Imports.razor │ ├── _Host.cshtml │ └── _Layout.cshtml ├── Program.cs ├── Properties │ └── launchSettings.json ├── RedirectToLogin.razor ├── Shared │ ├── LoginLayout.razor │ ├── MainLayout.razor │ ├── MainLayout.razor.css │ ├── NavMenu.razor │ ├── NavMenu.razor.css │ └── SurveyPrompt.razor ├── _Imports.razor ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ ├── css │ ├── bootstrap │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ ├── open-iconic │ │ ├── FONT-LICENSE │ │ ├── ICON-LICENSE │ │ ├── README.md │ │ └── font │ │ │ ├── css │ │ │ └── open-iconic-bootstrap.min.css │ │ │ └── fonts │ │ │ ├── open-iconic.eot │ │ │ ├── open-iconic.otf │ │ │ ├── open-iconic.svg │ │ │ ├── open-iconic.ttf │ │ │ └── open-iconic.woff │ └── site.css │ ├── favicon.ico │ └── images │ ├── Argo-C.png │ ├── Argo.png │ ├── bg2.jpg │ └── bg3.png └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ -------------------------------------------------------------------------------- /BlazorLearn.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorLearn", "BlazorLearn\BlazorLearn.csproj", "{635B95EF-694A-4FAB-907E-9A9A61204CB2}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {635B95EF-694A-4FAB-907E-9A9A61204CB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {635B95EF-694A-4FAB-907E-9A9A61204CB2}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {635B95EF-694A-4FAB-907E-9A9A61204CB2}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {635B95EF-694A-4FAB-907E-9A9A61204CB2}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /BlazorLearn/AdminHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using BlazorLearn.Entity; 3 | using Furion.Authorization; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Components; 6 | using RouteData = Microsoft.AspNetCore.Components.RouteData; 7 | 8 | namespace BlazorLearn; 9 | 10 | public class AdminHandler : AppAuthorizeHandler 11 | { 12 | public override Task PipelineAsync(AuthorizationHandlerContext context, DefaultHttpContext httpContext) 13 | { 14 | if (!int.TryParse(context.User.FindFirst(ClaimTypes.Role)?.Value, out var roleId)) 15 | { 16 | return Task.FromResult(false); 17 | } 18 | if (context.Resource is RouteData routeData) 19 | { 20 | var routeAttr = routeData.PageType.CustomAttributes.FirstOrDefault(x => 21 | x.AttributeType == typeof(RouteAttribute)); 22 | if (routeAttr == null) 23 | { 24 | return Task.FromResult(true); 25 | } 26 | else 27 | { 28 | var url = routeAttr.ConstructorArguments[0].Value as string; 29 | var permission = MenuEntity 30 | .Where(x => x.Roles!.Any(y => y.Id == roleId) && x.Url == url).First(); 31 | if (permission != null) 32 | { 33 | return Task.FromResult(true); 34 | } 35 | } 36 | } 37 | else 38 | { 39 | return Task.FromResult(true); 40 | } 41 | 42 | return Task.FromResult(false); 43 | } 44 | } -------------------------------------------------------------------------------- /BlazorLearn/App.razor: -------------------------------------------------------------------------------- 1 | @using System.Security.Claims 2 | @using BlazorLearn.Entity 3 | 4 | @inject NavigationManager NavigationManager 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Not found 18 | 19 |

Sorry, there's nothing at this address.

20 |
21 |
22 |
23 |
24 | 25 | @code { 26 | 27 | private void PermissionCheck(NavigationContext context) 28 | { 29 | var whiteList = Furion.App.Configuration["WhiteList"]; 30 | if (whiteList != null && whiteList.Split(',').Contains(context.Path)) 31 | { 32 | return; 33 | } 34 | 35 | var user = Furion.App.User; 36 | if (user == null) 37 | { 38 | NavigationManager.NavigateTo("/Login"); 39 | return; 40 | } 41 | 42 | if (user.Identity?.IsAuthenticated != true) 43 | { 44 | NavigationManager.NavigateTo("/Login"); 45 | return; 46 | } 47 | 48 | if (!int.TryParse(user.FindFirst(ClaimTypes.Role)?.Value, out var roleId)) 49 | { 50 | NavigationManager.NavigateTo("/Login"); 51 | return; 52 | } 53 | 54 | var permission = MenuEntity 55 | .Where(x => x.Roles!.Any(y => y.Id == roleId) && x.Url == context.Path).First(); 56 | 57 | if (permission == null) 58 | { 59 | NavigationManager.NavigateTo("/Login"); 60 | } 61 | 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /BlazorLearn/BlazorLearn.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /BlazorLearn/Controller/AccountController.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using BlazorLearn.Entity; 3 | using BlazorLearn.Pages.Account; 4 | using BootstrapBlazor.Components; 5 | using Furion.DataEncryption; 6 | using Furion.DynamicApiController; 7 | using Microsoft.AspNetCore.Authentication; 8 | using Microsoft.AspNetCore.Authentication.Cookies; 9 | using Microsoft.AspNetCore.Authorization; 10 | using Microsoft.AspNetCore.Mvc; 11 | 12 | namespace BlazorLearn.Controller; 13 | 14 | public class AccountController: IDynamicApiController 15 | { 16 | public async Task PostLogin([FromBody]LoginVo loginVo) 17 | { 18 | if (string.IsNullOrEmpty(loginVo.UserName)) 19 | { 20 | return new { code = 50000, message = "用户名不能为空" }; 21 | } 22 | if (string.IsNullOrEmpty(loginVo.Password)) 23 | { 24 | return new { code = 50000, message = "密码不能为空" }; 25 | } 26 | 27 | var password = MD5Encryption.Encrypt(loginVo.Password); 28 | var user = await UserEntity.Where(x => 29 | x.UserName == loginVo.UserName && x.Password == password).Include(x => x.Role).FirstAsync(); 30 | if (user != null) 31 | { 32 | var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); 33 | identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName!)); 34 | identity.AddClaim(new Claim(ClaimTypes.Role, user.Role!.Id.ToString())); 35 | await Furion.App.HttpContext.SignInAsync(new ClaimsPrincipal(identity), new AuthenticationProperties(){IsPersistent = true, ExpiresUtc = loginVo.RememberMe? DateTimeOffset.Now.AddDays(5): DateTimeOffset.Now.AddMinutes(30)}); 36 | 37 | return new { code = 20000, message = "登录成功" }; 38 | } 39 | return new { code = 50000, message = "用户名或密码错误" }; 40 | } 41 | 42 | [Authorize] 43 | public async Task GetLogout() 44 | { 45 | await Furion.App.HttpContext.SignOutAsync(); 46 | return new RedirectResult("/Login"); 47 | } 48 | } -------------------------------------------------------------------------------- /BlazorLearn/Data/ChangePassword.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.ComponentModel.DataAnnotations; 3 | using BootstrapBlazor.Components; 4 | 5 | namespace BlazorLearn.Data; 6 | 7 | public class ChangePassword 8 | { 9 | [Display(Name = "原密码")] 10 | [Required(ErrorMessage = "原密码不能为空")] 11 | [AutoGenerateColumn(ComponentType = typeof(BootstrapPassword))] 12 | public string? OldPassword { get; set; } 13 | 14 | [Display(Name = "新密码")] 15 | [Required(ErrorMessage = "新密码不能为空")] 16 | [AutoGenerateColumn(ComponentType = typeof(BootstrapPassword))] 17 | public string? NewPassword { get; set; } 18 | 19 | [Display(Name = "重复新密码")] 20 | [Compare(nameof(NewPassword), ErrorMessage = "两次密码不一致")] 21 | [AutoGenerateColumn(ComponentType = typeof(BootstrapPassword))] 22 | public string? RePassword { get; set; } 23 | } -------------------------------------------------------------------------------- /BlazorLearn/Data/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorLearn.Data; 2 | 3 | public class WeatherForecast 4 | { 5 | public DateTime Date { get; set; } 6 | 7 | public int TemperatureC { get; set; } 8 | 9 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 10 | 11 | public string? Summary { get; set; } 12 | } -------------------------------------------------------------------------------- /BlazorLearn/Data/WeatherForecastService.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorLearn.Data; 2 | 3 | public class WeatherForecastService 4 | { 5 | private static readonly string[] Summaries = new[] 6 | { 7 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 8 | }; 9 | 10 | public Task GetForecastAsync(DateTime startDate) 11 | { 12 | return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast 13 | { 14 | Date = startDate.AddDays(index), 15 | TemperatureC = Random.Shared.Next(-20, 55), 16 | Summary = Summaries[Random.Shared.Next(Summaries.Length)] 17 | }).ToArray()); 18 | } 19 | } -------------------------------------------------------------------------------- /BlazorLearn/Entity/MenuEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using FreeSql; 3 | using FreeSql.DataAnnotations; 4 | 5 | namespace BlazorLearn.Entity; 6 | 7 | [Description("菜单表")] 8 | public class MenuEntity: BaseEntity 9 | { 10 | [Description("菜单名")] 11 | public string? Name { get; set; } 12 | 13 | [Description("菜单图标")] 14 | public string? Icon { get; set; } 15 | 16 | [Description("对应页面Url")] 17 | public string? Url { get; set; } 18 | 19 | [Description("父菜单ID")] 20 | public int ParentId { get; set; } 21 | 22 | [Navigate(nameof(ParentId))] 23 | public MenuEntity? Parent { get; set; } 24 | 25 | [Navigate(nameof(ParentId))] 26 | public List? Children { get; set; } 27 | 28 | [Description("角色")] 29 | [Navigate(ManyToMany = typeof(RoleMenuEntity))] 30 | public virtual ICollection? Roles { get; set; } 31 | } -------------------------------------------------------------------------------- /BlazorLearn/Entity/RoleEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using FreeSql; 3 | using FreeSql.DataAnnotations; 4 | 5 | namespace BlazorLearn.Entity; 6 | 7 | [Description("角色表")] 8 | public class RoleEntity : BaseEntity 9 | { 10 | [Description("角色名称")] 11 | public string? Name { get; set; } 12 | 13 | [Description("用户")] 14 | [Navigate(nameof(UserEntity.RoleId))] 15 | public virtual ICollection? Users { get; set; } 16 | 17 | [Description("权限")] 18 | [Navigate(ManyToMany = typeof(RoleMenuEntity))] 19 | public virtual ICollection? Permissions { get; set; } 20 | } -------------------------------------------------------------------------------- /BlazorLearn/Entity/RoleMenuEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using FreeSql; 3 | using FreeSql.DataAnnotations; 4 | 5 | namespace BlazorLearn.Entity; 6 | 7 | [Description("角色权限多多关系表")] 8 | public class RoleMenuEntity : BaseEntity 9 | { 10 | [Description("角色Id")] 11 | public int RoleId { get; set; } 12 | 13 | [Description("角色")] 14 | [Navigate(nameof(RoleId))] 15 | public RoleEntity? Role { get; set; } 16 | 17 | [Description("权限Id")] 18 | public int PermissionId { get; set; } 19 | 20 | [Description("权限")] 21 | [Navigate(nameof(PermissionId))] 22 | public MenuEntity? Permission { get; set; } 23 | } -------------------------------------------------------------------------------- /BlazorLearn/Entity/UserEntity.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using FreeSql; 3 | using FreeSql.DataAnnotations; 4 | 5 | namespace BlazorLearn.Entity; 6 | 7 | [Description("用户信息表")] 8 | public class UserEntity : BaseEntity 9 | { 10 | [Description("用户名")] 11 | public string? UserName { get; set; } 12 | 13 | [Description("密码")] 14 | public string? Password { get; set; } 15 | 16 | [Description("用户姓名")] 17 | public string? Name { get; set; } 18 | 19 | [Description("角色Id")] 20 | public int RoleId { get; set; } 21 | 22 | [Description("角色")] 23 | [Navigate(nameof(RoleId))] 24 | public RoleEntity? Role { get; set; } 25 | } -------------------------------------------------------------------------------- /BlazorLearn/Extensions/DbExtension.cs: -------------------------------------------------------------------------------- 1 | using BlazorLearn.Entity; 2 | using FreeSql; 3 | using Furion.DataEncryption; 4 | 5 | namespace BlazorLearn.Extensions; 6 | 7 | public static class DbExtension 8 | { 9 | public static IServiceCollection AddDb(this IServiceCollection services) 10 | { 11 | var conn = Furion.App.Configuration["Db:ConnString"]; 12 | var freeSql = new FreeSqlBuilder() 13 | .UseAutoSyncStructure(Furion.App.WebHostEnvironment.IsDevelopment()) 14 | .UseConnectionString(DataType.Sqlite, conn) 15 | .Build(); 16 | 17 | freeSql.Aop.ConfigEntity += (s, e) => 18 | { 19 | e.ModifyResult.Name = e.EntityType.Name.Replace("Entity", ""); 20 | }; 21 | 22 | BaseEntity.Initialization(freeSql, null); 23 | 24 | if (!UserEntity.Where(x => x.UserName == "Admin").Any()) 25 | { 26 | UserEntity user = new UserEntity() 27 | { 28 | UserName = "Admin", 29 | Password = MD5Encryption.Encrypt("Admin"), 30 | Name = "张三" 31 | }; 32 | user.Save(); 33 | 34 | 35 | MenuEntity homeMenu = new MenuEntity() 36 | { 37 | Name = "首页", 38 | Url = "/", 39 | Icon = "fa fa-home" 40 | }; 41 | homeMenu.Save(); 42 | 43 | var menuMenu = new MenuEntity() 44 | { 45 | Name = "菜单管理", 46 | Url = "/menu", 47 | Icon = "fa fa-bars", 48 | Sort = 10 49 | }; 50 | menuMenu.Save(); 51 | 52 | var roleMenu = new MenuEntity() 53 | { 54 | Name = "角色管理", 55 | Url = "/role", 56 | Icon = "fa fa-sitemap", 57 | Sort = 20 58 | }; 59 | roleMenu.Save(); 60 | 61 | MenuEntity userMenu = new MenuEntity() 62 | { 63 | Name = "用户管理", 64 | Url = "/user", 65 | Icon = "fa fa-user", 66 | Sort = 30 67 | }; 68 | userMenu.Save(); 69 | 70 | var parentTest = new MenuEntity() 71 | { 72 | Name = "父菜单", 73 | Url = "/parent" 74 | }; 75 | parentTest.Save(); 76 | 77 | var child1 = new MenuEntity() 78 | { 79 | Name = "子菜单1", 80 | Url = "/child1", 81 | ParentId = parentTest.Id 82 | }; 83 | child1.Save(); 84 | 85 | var child2 = new MenuEntity() 86 | { 87 | Name = "子菜单2", 88 | Url = "/child2", 89 | ParentId = parentTest.Id 90 | }; 91 | child2.Save(); 92 | 93 | RoleEntity role = new RoleEntity() 94 | { 95 | Name = "管理员", 96 | Users = new List() { user }, 97 | Permissions = new List(){homeMenu, menuMenu, roleMenu, userMenu, parentTest, child1, child2} 98 | }; 99 | role.Save().SaveMany(nameof(RoleEntity.Users)); 100 | role.SaveMany(nameof(RoleEntity.Permissions)); 101 | } 102 | 103 | 104 | return services; 105 | } 106 | } -------------------------------------------------------------------------------- /BlazorLearn/Pages/Account/Login.razor: -------------------------------------------------------------------------------- 1 | @page "/Login" 2 | @layout LoginLayout 3 | 4 | 5 |
6 |
7 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /BlazorLearn/Pages/Account/Login.razor.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using BootstrapBlazor.Components; 3 | using Furion.ClayObject; 4 | using Microsoft.AspNetCore.Components; 5 | 6 | namespace BlazorLearn.Pages.Account; 7 | 8 | public partial class Login 9 | { 10 | private string Title { get; set; } = "登录"; 11 | 12 | [SupplyParameterFromQuery] 13 | [Parameter] 14 | public string? ReturnUrl { get; set; } 15 | 16 | private LoginVo LoginVo { get; set; } = new LoginVo(); 17 | 18 | [Inject] 19 | [NotNull] 20 | private AjaxService? AjaxService { get; set; } 21 | 22 | [Inject] 23 | [NotNull] 24 | public MessageService? MessageService { get; set; } 25 | 26 | private Task OnSignUp() 27 | { 28 | throw new NotImplementedException(); 29 | } 30 | 31 | private Task OnForgotPassword() 32 | { 33 | throw new NotImplementedException(); 34 | } 35 | 36 | private async Task DoLogin() 37 | { 38 | if (string.IsNullOrEmpty(LoginVo.UserName)) 39 | { 40 | await MessageService.Show(new MessageOption() 41 | { 42 | Color = Color.Danger, 43 | Content = "用户名不能为空" 44 | }); 45 | return; 46 | } 47 | 48 | if (string.IsNullOrEmpty(LoginVo.Password)) 49 | { 50 | await MessageService.Show(new MessageOption() 51 | { 52 | Color = Color.Danger, 53 | Content = "密码不能为空" 54 | }); 55 | return; 56 | } 57 | 58 | var ajaxOption = new AjaxOption 59 | { 60 | Url = "/api/account/login", 61 | Data = LoginVo 62 | }; 63 | var str = await AjaxService.GetMessage(ajaxOption); 64 | if (string.IsNullOrEmpty(str)) 65 | { 66 | await MessageService.Show(new MessageOption() 67 | { 68 | Color = Color.Danger, 69 | Content = "登录失败" 70 | }); 71 | } 72 | else 73 | { 74 | dynamic ret = Clay.Parse(str); 75 | if (ret.code != 20000) 76 | { 77 | await MessageService.Show(new MessageOption() 78 | { 79 | Color = Color.Danger, 80 | Content = ret.message 81 | }); 82 | } 83 | else 84 | { 85 | await MessageService.Show(new MessageOption() 86 | { 87 | Color = Color.Success, 88 | Content = "登录成功" 89 | }); 90 | ReturnUrl ??= "/"; 91 | await AjaxService.Goto(ReturnUrl); 92 | } 93 | } 94 | } 95 | } 96 | 97 | public class LoginVo 98 | { 99 | public string? UserName { get; set; } 100 | 101 | public string? Password { get; set; } 102 | 103 | public bool RememberMe { get; set; } 104 | } -------------------------------------------------------------------------------- /BlazorLearn/Pages/Account/Login.razor.css: -------------------------------------------------------------------------------- 1 | .wrap { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | right: 0; 6 | bottom: 0; 7 | } 8 | 9 | @media (min-width: 768px) { 10 | .wrap { 11 | background-color: #5bc0de; 12 | background: url('/images/bg2.jpg') fixed no-repeat; 13 | background-size: 100% 100%; 14 | } 15 | 16 | .container { 17 | background: url('/images/bg3.png') no-repeat; 18 | background-size: contain; 19 | width: 704px; 20 | height: 404px; 21 | margin: 0 auto; 22 | margin-top: calc(100vh / 2 - 190px); 23 | } 24 | } 25 | 26 | .form-signin-heading { 27 | margin: 0; 28 | padding: 20px 15px; 29 | text-align: center; 30 | background-color: #41cac0; 31 | border-radius: 5px 5px 0 0; 32 | color: #fff; 33 | } 34 | 35 | .login-wrap { 36 | display: flex; 37 | flex-direction: column; 38 | margin: 1rem; 39 | } 40 | 41 | .login-wrap .card, .login-wrap .card:hover, .modal .card, .modal .card:hover { 42 | border: 1px solid #84bbe2; 43 | } 44 | 45 | .login-sms, 46 | .is-mobile .login-up { 47 | display: none; 48 | } 49 | 50 | .is-mobile .login-sms { 51 | display: block; 52 | } 53 | 54 | ::deep .btn-login { 55 | background: #f67a6e; 56 | border-color: transparent; 57 | color: #fff; 58 | text-transform: uppercase; 59 | font-weight: 300; 60 | font-family: 'Open Sans', sans-serif; 61 | box-shadow: 0 4px #e56b60; 62 | line-height: 1; 63 | } 64 | 65 | ::deep .btn-login:focus { 66 | box-shadow: 0 4px #e56b60; 67 | } 68 | 69 | ::deep .input-group-text { 70 | background-color: #6bc3f4; 71 | border-color: #1ca0e9; 72 | } 73 | 74 | ::deep .form-check-label { 75 | color: #212529; 76 | } 77 | 78 | ::deep .form-control { 79 | border-color: #1ca0e9 !important; 80 | } 81 | 82 | ::deep .divider-wrap { 83 | background-color: #7c86bb; 84 | } 85 | 86 | ::deep .divider-text { 87 | background-color: #a7daf9; 88 | } 89 | 90 | .login-list { 91 | display: flex; 92 | justify-content: space-between; 93 | } 94 | 95 | .login-list ::deep img { 96 | width: 32px; 97 | height: 32px; 98 | } 99 | 100 | ::deep .btn-sms { 101 | width: 140px; 102 | } 103 | 104 | @media (max-width: 767px) { 105 | .form-signin { 106 | margin: 0 auto; 107 | background: #fff; 108 | border-radius: 5px; 109 | max-width: 320px; 110 | border: solid 1px #ddd; 111 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.125); 112 | } 113 | } 114 | 115 | @media (min-width: 768px) { 116 | .form-signin-heading { 117 | padding: 28px 0; 118 | background-color: transparent; 119 | } 120 | 121 | .login-wrap { 122 | width: 300px; 123 | height: 274px; 124 | position: relative; 125 | left: 346px; 126 | justify-content: center; 127 | } 128 | 129 | .slidercaptcha { 130 | width: 300px; 131 | } 132 | 133 | .slidercaptcha, .slidercaptcha.oauth { 134 | height: 280px; 135 | } 136 | 137 | .slidercaptcha.card .card-body { 138 | padding: 15px 15px 0 15px; 139 | } 140 | 141 | .login-footer { 142 | width: 100%; 143 | display: flex; 144 | justify-content: space-around; 145 | } 146 | 147 | .login-footer .login-footer-body li a { 148 | color: #e0e0e0; 149 | } 150 | } 151 | 152 | @media (min-width: 768px) { 153 | .gitee.wrap { 154 | background-color: #f1f2f7; 155 | background-image: none; 156 | } 157 | 158 | .gitee .container { 159 | width: 704px; 160 | height: 404px; 161 | margin: 0 auto; 162 | margin-top: calc(100vh / 2 - 190px); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /BlazorLearn/Pages/Admin/Counter.razor: -------------------------------------------------------------------------------- 1 | @page "/counter" 2 | @attribute [Authorize] 3 | 4 | Counter 5 | 6 |

Counter

7 | 8 |

Current count: @currentCount

9 | 10 | 11 | 12 | @code { 13 | private int currentCount = 0; 14 | 15 | private void IncrementCount() 16 | { 17 | currentCount++; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /BlazorLearn/Pages/Admin/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model BlazorLearn.Pages.ErrorModel 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Error 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Error.

19 |

An error occurred while processing your request.

20 | 21 | @if (Model.ShowRequestId) 22 | { 23 |

24 | Request ID: @Model.RequestId 25 |

26 | } 27 | 28 |

Development Mode

29 |

30 | Swapping to the Development environment displays detailed information about the error that occurred. 31 |

32 |

33 | The Development environment shouldn't be enabled for deployed applications. 34 | It can result in displaying sensitive information from exceptions to end users. 35 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 36 | and restarting the app. 37 |

38 |
39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /BlazorLearn/Pages/Admin/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | 5 | namespace BlazorLearn.Pages; 6 | 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | public class ErrorModel : PageModel 10 | { 11 | public string? RequestId { get; set; } 12 | 13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 14 | 15 | private readonly ILogger _logger; 16 | 17 | public ErrorModel(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 25 | } 26 | } -------------------------------------------------------------------------------- /BlazorLearn/Pages/Admin/FetchData.razor: -------------------------------------------------------------------------------- 1 | @page "/fetchdata" 2 | 3 | Weather forecast 4 | 5 | @using BlazorLearn.Data 6 | @inject WeatherForecastService ForecastService 7 | 8 |

Weather forecast

9 | 10 |

This component demonstrates fetching data from a service.

11 | 12 | @if (forecasts == null) 13 | { 14 |

15 | Loading... 16 |

17 | } 18 | else 19 | { 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | @foreach (var forecast in forecasts) 31 | { 32 | 33 | 34 | 35 | 36 | 37 | 38 | } 39 | 40 |
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
41 | } 42 | 43 | @code { 44 | private WeatherForecast[]? forecasts; 45 | 46 | protected override async Task OnInitializedAsync() 47 | { 48 | forecasts = await ForecastService.GetForecastAsync(DateTime.Now); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /BlazorLearn/Pages/Admin/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @attribute [Authorize] 3 | 4 | Index 5 | 6 |

Hello, world!

7 | 8 | Welcome to your new app. 9 | 10 | -------------------------------------------------------------------------------- /BlazorLearn/Pages/Admin/Menu.razor: -------------------------------------------------------------------------------- 1 | @page "/menu" 2 | @using BlazorLearn.Entity 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | @code { 16 | 17 | private List Menus { get; set; } 18 | 19 | protected override void OnInitialized() 20 | { 21 | base.OnInitialized(); 22 | Menus = new List(); 23 | Menus.Add(new SelectedItem("", "顶级菜单")); 24 | Menus.AddRange(MenuEntity.Select.ToList(x => new SelectedItem(x.Id.ToString(), x.Name!))); 25 | } 26 | 27 | private Task> OnQueryAsync(QueryPageOptions arg) 28 | { 29 | var items = MenuEntity.Select.ToTreeList(); 30 | return Task.FromResult(new QueryData() 31 | { 32 | Items = items, 33 | TotalCount = items.Count 34 | }); 35 | } 36 | 37 | private Task OnSaveAsync(MenuEntity arg1, ItemChangedType arg2) 38 | { 39 | arg1.Save(); 40 | return Task.FromResult(true); 41 | } 42 | 43 | private Task>> TreeNodeConverter(IEnumerable arg) 44 | { 45 | return Task.FromResult(arg.Select(x => new TableTreeNode(x) 46 | { 47 | HasChildren = x.Children is {Count: > 0 } 48 | })); 49 | } 50 | 51 | private Task>> OnTreeExpand(MenuEntity arg) 52 | { 53 | return Task.FromResult(arg.Children!.Select(x => new TableTreeNode(x) 54 | { 55 | HasChildren = x.Children is {Count: > 0 } 56 | })); 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /BlazorLearn/Pages/Admin/Role.razor: -------------------------------------------------------------------------------- 1 | @page "/role" 2 | @using BlazorLearn.Entity 3 | @inject IDispatchService DispatchService 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | @code { 28 | 29 | private Modal? RoleModal { get; set; } 30 | 31 | private List>? Menus { get; set; } 32 | 33 | private RoleEntity? RoleEntity { get; set; } 34 | 35 | private Task> OnQueryAsync(QueryPageOptions arg) 36 | { 37 | var roles = RoleEntity.Select.IncludeMany(x => x.Permissions).Page(arg.PageIndex, arg.PageItems) 38 | .Count(out var count).ToList(); 39 | return Task.FromResult>(new QueryData() 40 | { 41 | TotalCount = (int)count, 42 | Items = roles 43 | }); 44 | } 45 | 46 | private void RoleClick(RoleEntity roleEntity) 47 | { 48 | RoleEntity = roleEntity; 49 | Menus = CascadingTree(MenuEntity.Select.ToList(), roleEntity.Permissions, new TreeViewItem(new MenuEntity())).ToList(); 50 | RoleModal?.Toggle(); 51 | } 52 | 53 | private IEnumerable> CascadingTree(IEnumerable items, IEnumerable? selectedItems, TreeViewItem parent) => items.Where(i => i.ParentId == parent.Value.Id).Select(i => 54 | { 55 | var item = new TreeViewItem(i) 56 | { 57 | Text = i.Name, 58 | Icon = i.Icon, 59 | Value = i 60 | }; 61 | item.Items = CascadingTree(items, selectedItems, item).ToList(); 62 | item.Parent = parent; 63 | if (selectedItems?.Any(x => x.Id == i.Id) == true) 64 | { 65 | item.CheckedState = CheckboxState.Checked; 66 | } 67 | return item; 68 | }); 69 | 70 | private Task OnSaveAsync(RoleEntity arg1, ItemChangedType arg2) 71 | { 72 | arg1.Save(); 73 | return Task.FromResult(true); 74 | } 75 | 76 | private void SavePermission() 77 | { 78 | if (RoleEntity == null) 79 | { 80 | return; 81 | } 82 | var menus = new List(); 83 | SaveRole(Menus!.Where(x => x.CheckedState != CheckboxState.UnChecked), menus); 84 | RoleEntity.Permissions = menus; 85 | RoleEntity.SaveMany(nameof(RoleEntity.Permissions)); 86 | RoleModal?.Toggle(); 87 | DispatchService.Dispatch(new DispatchEntry(){Entry = "role"}); 88 | } 89 | 90 | private void SaveRole(IEnumerable> items, List menus) 91 | { 92 | menus.AddRange(items.Select(x => x.Value)); 93 | foreach (var treeViewItem in items) 94 | { 95 | if (treeViewItem.Items.Any(x => x.CheckedState != CheckboxState.UnChecked)) 96 | { 97 | SaveRole(treeViewItem.Items.Where(x => x.CheckedState != CheckboxState.UnChecked), menus); 98 | } 99 | } 100 | } 101 | 102 | } -------------------------------------------------------------------------------- /BlazorLearn/Pages/Admin/User.razor: -------------------------------------------------------------------------------- 1 | @page "/user" 2 | @using BlazorLearn.Entity 3 | @using Furion.DataEncryption 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | @code { 30 | 31 | private List? Roles { get; set; } 32 | 33 | private Modal? PasswordModal { get; set; } 34 | 35 | private UserEntity? SelectedUser { get; set; } 36 | 37 | protected override void OnInitialized() 38 | { 39 | base.OnInitialized(); 40 | SelectedUser = new UserEntity(); 41 | Roles = RoleEntity.Select.ToList().Select(x => new SelectedItem(x.Id.ToString(), x.Name!)).ToList(); 42 | } 43 | 44 | private Task> OnQueryAsync(QueryPageOptions arg) 45 | { 46 | var users = UserEntity.Select.Count(out var count) 47 | .Page(arg.PageIndex, arg.PageItems).ToList(); 48 | return Task.FromResult(new QueryData() 49 | { 50 | Items = users, 51 | TotalCount = (int)count 52 | }); 53 | } 54 | 55 | private Task OnSaveAsync(UserEntity arg1, ItemChangedType arg2) 56 | { 57 | if (arg2 == ItemChangedType.Add) 58 | { 59 | arg1.Password = MD5Encryption.Encrypt(arg1.UserName); 60 | } 61 | arg1.Save(); 62 | return Task.FromResult(true); 63 | } 64 | 65 | private void ResetPassword() 66 | { 67 | SelectedUser.Password = MD5Encryption.Encrypt(SelectedUser.Password); 68 | SelectedUser?.Save(); 69 | PasswordModal?.Toggle(); 70 | } 71 | 72 | private Task ShowModal(UserEntity userEntity) 73 | { 74 | SelectedUser = userEntity; 75 | SelectedUser.Password = ""; 76 | PasswordModal?.Toggle(); 77 | StateHasChanged(); 78 | return Task.CompletedTask; 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /BlazorLearn/Pages/Admin/_Imports.razor: -------------------------------------------------------------------------------- 1 | @attribute [Authorize] -------------------------------------------------------------------------------- /BlazorLearn/Pages/_Host.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @namespace BlazorLearn.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | @{ 5 | Layout = "_Layout"; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /BlazorLearn/Pages/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web 2 | @namespace BlazorLearn.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | @RenderBody() 19 | 20 |
21 | 22 | An error has occurred. This application may no longer respond until reloaded. 23 | 24 | 25 | An unhandled exception has occurred. See browser dev tools for details. 26 | 27 | Reload 28 | 🗙 29 |
30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /BlazorLearn/Program.cs: -------------------------------------------------------------------------------- 1 | using BlazorLearn; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.AspNetCore.Components.Web; 4 | using BlazorLearn.Data; 5 | using BlazorLearn.Extensions; 6 | using Microsoft.AspNetCore.Authentication.Cookies; 7 | 8 | var builder = WebApplication.CreateBuilder(args).Inject(); 9 | 10 | builder.Services.AddAppAuthorization(); 11 | // Add services to the container. 12 | builder.Services.AddRazorPages(); 13 | 14 | builder.Services.AddControllers().AddInject(); 15 | 16 | builder.Services.AddServerSideBlazor(); 17 | builder.Services.AddSingleton(); 18 | builder.Services.AddBootstrapBlazor(); 19 | 20 | builder.Services.AddDb(); 21 | 22 | 23 | builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(); 24 | 25 | var app = builder.Build(); 26 | 27 | // Configure the HTTP request pipeline. 28 | if (!app.Environment.IsDevelopment()) 29 | { 30 | app.UseExceptionHandler("/Error"); 31 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 32 | app.UseHsts(); 33 | } 34 | 35 | app.UseHttpsRedirection(); 36 | 37 | app.UseInject(); 38 | 39 | app.UseStaticFiles(); 40 | 41 | app.UseRouting(); 42 | 43 | app.UseAuthentication(); 44 | app.UseAuthorization(); 45 | 46 | app.MapDefaultControllerRoute(); 47 | app.MapBlazorHub(); 48 | app.MapFallbackToPage("/_Host"); 49 | 50 | app.Run(); -------------------------------------------------------------------------------- /BlazorLearn/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:43962", 7 | "sslPort": 44365 8 | } 9 | }, 10 | "profiles": { 11 | "BlazorLearn": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "applicationUrl": "https://localhost:7199;http://localhost:5199", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "IIS Express": { 21 | "commandName": "IISExpress", 22 | "launchBrowser": true, 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BlazorLearn/RedirectToLogin.razor: -------------------------------------------------------------------------------- 1 | @inject NavigationManager NavigationManager 2 | 3 | @code { 4 | 5 | protected override void OnAfterRender(bool firstRender) 6 | { 7 | base.OnAfterRender(firstRender); 8 | NavigationManager.NavigateTo("/Login", true); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /BlazorLearn/Shared/LoginLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | @Body 4 | 5 | @code { 6 | 7 | } -------------------------------------------------------------------------------- /BlazorLearn/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @using BlazorLearn.Entity 2 | @using System.Security.Claims 3 | @using System.Diagnostics.CodeAnalysis 4 | @using BlazorLearn.Data 5 | @using FreeSql 6 | @using Furion.DataEncryption.Extensions 7 | @using App = Furion.App 8 | @inherits LayoutComponentBase 9 | @inject DialogService DialogService 10 | @inject ToastService ToastService 11 | @implements IDisposable 12 | @inject IDispatchService DispatchService 13 | 14 | BlazorLearn 15 | 16 | 18 |
19 | BlazorLearn 20 |
21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
31 | 32 |
33 | BlazorLearn 34 |
35 |
36 |
37 |
38 | 39 | @Body 40 | 41 |
42 | 47 |
48 | 49 | @code 50 | { 51 | private bool IsCollapsed { get; set; } 52 | 53 | private List? _menuItems; 54 | 55 | [NotNull] 56 | private UserEntity? _user; 57 | 58 | private Task OnCollapsed(bool collapsed) 59 | { 60 | IsCollapsed = collapsed; 61 | return Task.CompletedTask; 62 | } 63 | 64 | protected override void OnInitialized() 65 | { 66 | base.OnInitialized(); 67 | _user = UserEntity.Where(x => x.UserName == App.User.FindFirstValue(ClaimTypes.Name)).First(); 68 | if (_user == null) 69 | { 70 | return; 71 | } 72 | RefreshMenu(); 73 | DispatchService.Subscribe(Notify); 74 | } 75 | 76 | private Task Notify(DispatchEntry arg) 77 | { 78 | if (arg.Entry == "role") 79 | { 80 | RefreshMenu(); 81 | InvokeAsync(StateHasChanged); 82 | } 83 | return Task.CompletedTask; 84 | } 85 | 86 | private void RefreshMenu() 87 | { 88 | _menuItems = CreateMenuItems(MenuEntity.Where(x => x.Roles!.Any(y => y.Id == _user.RoleId)).ToList(), 0); 89 | } 90 | 91 | private List CreateMenuItems(List menus, int parentId) 92 | { 93 | var selectedMenus = new List(); 94 | var selectedMenuEntities = menus.Where(x => x.ParentId == parentId).ToList(); 95 | 96 | foreach (var menuEntity in selectedMenuEntities) 97 | { 98 | var menuItem = new MenuItem(menuEntity.Name!, menuEntity.Url, menuEntity.Icon); 99 | menuItem.Items = CreateMenuItems(menus, menuEntity.Id); 100 | selectedMenus.Add(menuItem); 101 | } 102 | return selectedMenus; 103 | } 104 | 105 | private void ChangePassword() 106 | { 107 | var option = new EditDialogOption() 108 | { 109 | Title = "修改密码", 110 | Size = Size.Medium, 111 | Model = new ChangePassword(), 112 | ItemsPerRow = 1, 113 | RowType = RowType.Normal, 114 | OnEditAsync = async context => 115 | { 116 | if (context.Model is not ChangePassword changePassword) 117 | { 118 | await ToastService.Show(new ToastOption() 119 | { 120 | Category = ToastCategory.Error, 121 | Title = "保存出错", 122 | Content = "类型转换错误" 123 | }); 124 | return false; 125 | } 126 | var oldPassword = changePassword.OldPassword.ToMD5Encrypt(); 127 | if (!await UserEntity.Select.Where(x => x.Id == _user.Id && x.Password == oldPassword).AnyAsync()) 128 | { 129 | await ToastService.Show(new ToastOption() 130 | { 131 | Category = ToastCategory.Error, 132 | Title = "保存出错", 133 | Content = "原密码不正确,请检查原密码" 134 | }); 135 | return false; 136 | } 137 | var newPassword = changePassword.NewPassword.ToMD5Encrypt(); 138 | _user.Password = newPassword; 139 | await _user.SaveAsync(); 140 | return true; 141 | } 142 | }; 143 | DialogService.ShowEditDialog(option); 144 | } 145 | 146 | public void Dispose() 147 | { 148 | DispatchService.UnSubscribe(Notify); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /BlazorLearn/Shared/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | } 28 | 29 | .top-row a:first-child { 30 | overflow: hidden; 31 | text-overflow: ellipsis; 32 | } 33 | 34 | @media (max-width: 640.98px) { 35 | .top-row:not(.auth) { 36 | display: none; 37 | } 38 | 39 | .top-row.auth { 40 | justify-content: space-between; 41 | } 42 | 43 | .top-row a, .top-row .btn-link { 44 | margin-left: 0; 45 | } 46 | } 47 | 48 | @media (min-width: 641px) { 49 | .page { 50 | flex-direction: row; 51 | } 52 | 53 | .sidebar { 54 | width: 250px; 55 | height: 100vh; 56 | position: sticky; 57 | top: 0; 58 | } 59 | 60 | .top-row { 61 | position: sticky; 62 | top: 0; 63 | z-index: 1; 64 | } 65 | 66 | .top-row, article { 67 | padding-left: 2rem !important; 68 | padding-right: 1.5rem !important; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /BlazorLearn/Shared/NavMenu.razor: -------------------------------------------------------------------------------- 1 |  9 | 10 |
11 | 28 |
29 | 30 | @code { 31 | private bool collapseNavMenu = true; 32 | 33 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; 34 | 35 | private void ToggleNavMenu() 36 | { 37 | collapseNavMenu = !collapseNavMenu; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /BlazorLearn/Shared/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1.1rem; 12 | } 13 | 14 | .oi { 15 | width: 2rem; 16 | font-size: 1.1rem; 17 | vertical-align: text-top; 18 | top: -2px; 19 | } 20 | 21 | .nav-item { 22 | font-size: 0.9rem; 23 | padding-bottom: 0.5rem; 24 | } 25 | 26 | .nav-item:first-of-type { 27 | padding-top: 1rem; 28 | } 29 | 30 | .nav-item:last-of-type { 31 | padding-bottom: 1rem; 32 | } 33 | 34 | .nav-item ::deep a { 35 | color: #d7d7d7; 36 | border-radius: 4px; 37 | height: 3rem; 38 | display: flex; 39 | align-items: center; 40 | line-height: 3rem; 41 | } 42 | 43 | .nav-item ::deep a.active { 44 | background-color: rgba(255,255,255,0.25); 45 | color: white; 46 | } 47 | 48 | .nav-item ::deep a:hover { 49 | background-color: rgba(255,255,255,0.1); 50 | color: white; 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .navbar-toggler { 55 | display: none; 56 | } 57 | 58 | .collapse { 59 | /* Never collapse the sidebar for wide screens */ 60 | display: block; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /BlazorLearn/Shared/SurveyPrompt.razor: -------------------------------------------------------------------------------- 1 | 
2 | 3 | @Title 4 | 5 | 6 | Please take our 7 | brief survey 8 | 9 | and tell us what you think. 10 |
11 | 12 | @code { 13 | // Demonstrates how a parent component can supply parameters 14 | [Parameter] 15 | public string? Title { get; set; } 16 | 17 | } -------------------------------------------------------------------------------- /BlazorLearn/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Authorization 3 | @using Microsoft.AspNetCore.Components.Authorization 4 | @using Microsoft.AspNetCore.Components.Forms 5 | @using Microsoft.AspNetCore.Components.Routing 6 | @using Microsoft.AspNetCore.Components.Web 7 | @using Microsoft.AspNetCore.Components.Web.Virtualization 8 | @using Microsoft.JSInterop 9 | @using BlazorLearn 10 | @using BlazorLearn.Shared 11 | @using BootstrapBlazor.Components -------------------------------------------------------------------------------- /BlazorLearn/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /BlazorLearn/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "Db": { 9 | "ConnString": "Data Source=|DataDirectory|\\document.db; Pooling=true;Min Pool Size=1" 10 | }, 11 | "AllowedHosts": "*", 12 | "WhiteList": "Login" 13 | } 14 | -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/css/open-iconic/FONT-LICENSE: -------------------------------------------------------------------------------- 1 | SIL OPEN FONT LICENSE Version 1.1 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | PREAMBLE 6 | The goals of the Open Font License (OFL) are to stimulate worldwide 7 | development of collaborative font projects, to support the font creation 8 | efforts of academic and linguistic communities, and to provide a free and 9 | open framework in which fonts may be shared and improved in partnership 10 | with others. 11 | 12 | The OFL allows the licensed fonts to be used, studied, modified and 13 | redistributed freely as long as they are not sold by themselves. The 14 | fonts, including any derivative works, can be bundled, embedded, 15 | redistributed and/or sold with any software provided that any reserved 16 | names are not used by derivative works. The fonts and derivatives, 17 | however, cannot be released under any other type of license. The 18 | requirement for fonts to remain under this license does not apply 19 | to any document created using the fonts or their derivatives. 20 | 21 | DEFINITIONS 22 | "Font Software" refers to the set of files released by the Copyright 23 | Holder(s) under this license and clearly marked as such. This may 24 | include source files, build scripts and documentation. 25 | 26 | "Reserved Font Name" refers to any names specified as such after the 27 | copyright statement(s). 28 | 29 | "Original Version" refers to the collection of Font Software components as 30 | distributed by the Copyright Holder(s). 31 | 32 | "Modified Version" refers to any derivative made by adding to, deleting, 33 | or substituting -- in part or in whole -- any of the components of the 34 | Original Version, by changing formats or by porting the Font Software to a 35 | new environment. 36 | 37 | "Author" refers to any designer, engineer, programmer, technical 38 | writer or other person who contributed to the Font Software. 39 | 40 | PERMISSION & CONDITIONS 41 | Permission is hereby granted, free of charge, to any person obtaining 42 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 43 | redistribute, and sell modified and unmodified copies of the Font 44 | Software, subject to the following conditions: 45 | 46 | 1) Neither the Font Software nor any of its individual components, 47 | in Original or Modified Versions, may be sold by itself. 48 | 49 | 2) Original or Modified Versions of the Font Software may be bundled, 50 | redistributed and/or sold with any software, provided that each copy 51 | contains the above copyright notice and this license. These can be 52 | included either as stand-alone text files, human-readable headers or 53 | in the appropriate machine-readable metadata fields within text or 54 | binary files as long as those fields can be easily viewed by the user. 55 | 56 | 3) No Modified Version of the Font Software may use the Reserved Font 57 | Name(s) unless explicit written permission is granted by the corresponding 58 | Copyright Holder. This restriction only applies to the primary font name as 59 | presented to the users. 60 | 61 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 62 | Software shall not be used to promote, endorse or advertise any 63 | Modified Version, except to acknowledge the contribution(s) of the 64 | Copyright Holder(s) and the Author(s) or with their explicit written 65 | permission. 66 | 67 | 5) The Font Software, modified or unmodified, in part or in whole, 68 | must be distributed entirely under this license, and must not be 69 | distributed under any other license. The requirement for fonts to 70 | remain under this license does not apply to any document created 71 | using the Font Software. 72 | 73 | TERMINATION 74 | This license becomes null and void if any of the above conditions are 75 | not met. 76 | 77 | DISCLAIMER 78 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 79 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 80 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 81 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 82 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 83 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 84 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 85 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 86 | OTHER DEALINGS IN THE FONT SOFTWARE. 87 | -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/css/open-iconic/README.md: -------------------------------------------------------------------------------- 1 | [Open Iconic v1.1.1](http://useiconic.com/open) 2 | =========== 3 | 4 | ### Open Iconic is the open source sibling of [Iconic](http://useiconic.com). It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap and Foundation. [View the collection](http://useiconic.com/open#icons) 5 | 6 | 7 | 8 | ## What's in Open Iconic? 9 | 10 | * 223 icons designed to be legible down to 8 pixels 11 | * Super-light SVG files - 61.8 for the entire set 12 | * SVG sprite—the modern replacement for icon fonts 13 | * Webfont (EOT, OTF, SVG, TTF, WOFF), PNG and WebP formats 14 | * Webfont stylesheets (including versions for Bootstrap and Foundation) in CSS, LESS, SCSS and Stylus formats 15 | * PNG and WebP raster images in 8px, 16px, 24px, 32px, 48px and 64px. 16 | 17 | 18 | ## Getting Started 19 | 20 | #### For code samples and everything else you need to get started with Open Iconic, check out our [Icons](http://useiconic.com/open#icons) and [Reference](http://useiconic.com/open#reference) sections. 21 | 22 | ### General Usage 23 | 24 | #### Using Open Iconic's SVGs 25 | 26 | We like SVGs and we think they're the way to display icons on the web. Since Open Iconic are just basic SVGs, we suggest you display them like you would any other image (don't forget the `alt` attribute). 27 | 28 | ``` 29 | icon name 30 | ``` 31 | 32 | #### Using Open Iconic's SVG Sprite 33 | 34 | Open Iconic also comes in a SVG sprite which allows you to display all the icons in the set with a single request. It's like an icon font, without being a hack. 35 | 36 | Adding an icon from an SVG sprite is a little different than what you're used to, but it's still a piece of cake. *Tip: To make your icons easily style able, we suggest adding a general class to the* `` *tag and a unique class name for each different icon in the* `` *tag.* 37 | 38 | ``` 39 | 40 | 41 | 42 | ``` 43 | 44 | Sizing icons only needs basic CSS. All the icons are in a square format, so just set the `` tag with equal width and height dimensions. 45 | 46 | ``` 47 | .icon { 48 | width: 16px; 49 | height: 16px; 50 | } 51 | ``` 52 | 53 | Coloring icons is even easier. All you need to do is set the `fill` rule on the `` tag. 54 | 55 | ``` 56 | .icon-account-login { 57 | fill: #f00; 58 | } 59 | ``` 60 | 61 | To learn more about SVG Sprites, read [Chris Coyier's guide](http://css-tricks.com/svg-sprites-use-better-icon-fonts/). 62 | 63 | #### Using Open Iconic's Icon Font... 64 | 65 | 66 | ##### …with Bootstrap 67 | 68 | You can find our Bootstrap stylesheets in `font/css/open-iconic-bootstrap.{css, less, scss, styl}` 69 | 70 | 71 | ``` 72 | 73 | ``` 74 | 75 | 76 | ``` 77 | 78 | ``` 79 | 80 | ##### …with Foundation 81 | 82 | You can find our Foundation stylesheets in `font/css/open-iconic-foundation.{css, less, scss, styl}` 83 | 84 | ``` 85 | 86 | ``` 87 | 88 | 89 | ``` 90 | 91 | ``` 92 | 93 | ##### …on its own 94 | 95 | You can find our default stylesheets in `font/css/open-iconic.{css, less, scss, styl}` 96 | 97 | ``` 98 | 99 | ``` 100 | 101 | ``` 102 | 103 | ``` 104 | 105 | 106 | ## License 107 | 108 | ### Icons 109 | 110 | All code (including SVG markup) is under the [MIT License](http://opensource.org/licenses/MIT). 111 | 112 | ### Fonts 113 | 114 | All fonts are under the [SIL Licensed](http://scripts.sil.org/cms/scripts/page.php?item_id=OFL_web). 115 | -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Icons;src:url(../fonts/open-iconic.eot);src:url(../fonts/open-iconic.eot?#iconic-sm) format('embedded-opentype'),url(../fonts/open-iconic.woff) format('woff'),url(../fonts/open-iconic.ttf) format('truetype'),url(../fonts/open-iconic.otf) format('opentype'),url(../fonts/open-iconic.svg#iconic-sm) format('svg');font-weight:400;font-style:normal}.oi{position:relative;top:1px;display:inline-block;speak:none;font-family:Icons;font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.oi:empty:before{width:1em;text-align:center;box-sizing:content-box}.oi.oi-align-center:before{text-align:center}.oi.oi-align-left:before{text-align:left}.oi.oi-align-right:before{text-align:right}.oi.oi-flip-horizontal:before{-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.oi.oi-flip-vertical:before{-webkit-transform:scale(1,-1);-ms-transform:scale(-1,1);transform:scale(1,-1)}.oi.oi-flip-horizontal-vertical:before{-webkit-transform:scale(-1,-1);-ms-transform:scale(-1,1);transform:scale(-1,-1)}.oi-account-login:before{content:'\e000'}.oi-account-logout:before{content:'\e001'}.oi-action-redo:before{content:'\e002'}.oi-action-undo:before{content:'\e003'}.oi-align-center:before{content:'\e004'}.oi-align-left:before{content:'\e005'}.oi-align-right:before{content:'\e006'}.oi-aperture:before{content:'\e007'}.oi-arrow-bottom:before{content:'\e008'}.oi-arrow-circle-bottom:before{content:'\e009'}.oi-arrow-circle-left:before{content:'\e00a'}.oi-arrow-circle-right:before{content:'\e00b'}.oi-arrow-circle-top:before{content:'\e00c'}.oi-arrow-left:before{content:'\e00d'}.oi-arrow-right:before{content:'\e00e'}.oi-arrow-thick-bottom:before{content:'\e00f'}.oi-arrow-thick-left:before{content:'\e010'}.oi-arrow-thick-right:before{content:'\e011'}.oi-arrow-thick-top:before{content:'\e012'}.oi-arrow-top:before{content:'\e013'}.oi-audio-spectrum:before{content:'\e014'}.oi-audio:before{content:'\e015'}.oi-badge:before{content:'\e016'}.oi-ban:before{content:'\e017'}.oi-bar-chart:before{content:'\e018'}.oi-basket:before{content:'\e019'}.oi-battery-empty:before{content:'\e01a'}.oi-battery-full:before{content:'\e01b'}.oi-beaker:before{content:'\e01c'}.oi-bell:before{content:'\e01d'}.oi-bluetooth:before{content:'\e01e'}.oi-bold:before{content:'\e01f'}.oi-bolt:before{content:'\e020'}.oi-book:before{content:'\e021'}.oi-bookmark:before{content:'\e022'}.oi-box:before{content:'\e023'}.oi-briefcase:before{content:'\e024'}.oi-british-pound:before{content:'\e025'}.oi-browser:before{content:'\e026'}.oi-brush:before{content:'\e027'}.oi-bug:before{content:'\e028'}.oi-bullhorn:before{content:'\e029'}.oi-calculator:before{content:'\e02a'}.oi-calendar:before{content:'\e02b'}.oi-camera-slr:before{content:'\e02c'}.oi-caret-bottom:before{content:'\e02d'}.oi-caret-left:before{content:'\e02e'}.oi-caret-right:before{content:'\e02f'}.oi-caret-top:before{content:'\e030'}.oi-cart:before{content:'\e031'}.oi-chat:before{content:'\e032'}.oi-check:before{content:'\e033'}.oi-chevron-bottom:before{content:'\e034'}.oi-chevron-left:before{content:'\e035'}.oi-chevron-right:before{content:'\e036'}.oi-chevron-top:before{content:'\e037'}.oi-circle-check:before{content:'\e038'}.oi-circle-x:before{content:'\e039'}.oi-clipboard:before{content:'\e03a'}.oi-clock:before{content:'\e03b'}.oi-cloud-download:before{content:'\e03c'}.oi-cloud-upload:before{content:'\e03d'}.oi-cloud:before{content:'\e03e'}.oi-cloudy:before{content:'\e03f'}.oi-code:before{content:'\e040'}.oi-cog:before{content:'\e041'}.oi-collapse-down:before{content:'\e042'}.oi-collapse-left:before{content:'\e043'}.oi-collapse-right:before{content:'\e044'}.oi-collapse-up:before{content:'\e045'}.oi-command:before{content:'\e046'}.oi-comment-square:before{content:'\e047'}.oi-compass:before{content:'\e048'}.oi-contrast:before{content:'\e049'}.oi-copywriting:before{content:'\e04a'}.oi-credit-card:before{content:'\e04b'}.oi-crop:before{content:'\e04c'}.oi-dashboard:before{content:'\e04d'}.oi-data-transfer-download:before{content:'\e04e'}.oi-data-transfer-upload:before{content:'\e04f'}.oi-delete:before{content:'\e050'}.oi-dial:before{content:'\e051'}.oi-document:before{content:'\e052'}.oi-dollar:before{content:'\e053'}.oi-double-quote-sans-left:before{content:'\e054'}.oi-double-quote-sans-right:before{content:'\e055'}.oi-double-quote-serif-left:before{content:'\e056'}.oi-double-quote-serif-right:before{content:'\e057'}.oi-droplet:before{content:'\e058'}.oi-eject:before{content:'\e059'}.oi-elevator:before{content:'\e05a'}.oi-ellipses:before{content:'\e05b'}.oi-envelope-closed:before{content:'\e05c'}.oi-envelope-open:before{content:'\e05d'}.oi-euro:before{content:'\e05e'}.oi-excerpt:before{content:'\e05f'}.oi-expand-down:before{content:'\e060'}.oi-expand-left:before{content:'\e061'}.oi-expand-right:before{content:'\e062'}.oi-expand-up:before{content:'\e063'}.oi-external-link:before{content:'\e064'}.oi-eye:before{content:'\e065'}.oi-eyedropper:before{content:'\e066'}.oi-file:before{content:'\e067'}.oi-fire:before{content:'\e068'}.oi-flag:before{content:'\e069'}.oi-flash:before{content:'\e06a'}.oi-folder:before{content:'\e06b'}.oi-fork:before{content:'\e06c'}.oi-fullscreen-enter:before{content:'\e06d'}.oi-fullscreen-exit:before{content:'\e06e'}.oi-globe:before{content:'\e06f'}.oi-graph:before{content:'\e070'}.oi-grid-four-up:before{content:'\e071'}.oi-grid-three-up:before{content:'\e072'}.oi-grid-two-up:before{content:'\e073'}.oi-hard-drive:before{content:'\e074'}.oi-header:before{content:'\e075'}.oi-headphones:before{content:'\e076'}.oi-heart:before{content:'\e077'}.oi-home:before{content:'\e078'}.oi-image:before{content:'\e079'}.oi-inbox:before{content:'\e07a'}.oi-infinity:before{content:'\e07b'}.oi-info:before{content:'\e07c'}.oi-italic:before{content:'\e07d'}.oi-justify-center:before{content:'\e07e'}.oi-justify-left:before{content:'\e07f'}.oi-justify-right:before{content:'\e080'}.oi-key:before{content:'\e081'}.oi-laptop:before{content:'\e082'}.oi-layers:before{content:'\e083'}.oi-lightbulb:before{content:'\e084'}.oi-link-broken:before{content:'\e085'}.oi-link-intact:before{content:'\e086'}.oi-list-rich:before{content:'\e087'}.oi-list:before{content:'\e088'}.oi-location:before{content:'\e089'}.oi-lock-locked:before{content:'\e08a'}.oi-lock-unlocked:before{content:'\e08b'}.oi-loop-circular:before{content:'\e08c'}.oi-loop-square:before{content:'\e08d'}.oi-loop:before{content:'\e08e'}.oi-magnifying-glass:before{content:'\e08f'}.oi-map-marker:before{content:'\e090'}.oi-map:before{content:'\e091'}.oi-media-pause:before{content:'\e092'}.oi-media-play:before{content:'\e093'}.oi-media-record:before{content:'\e094'}.oi-media-skip-backward:before{content:'\e095'}.oi-media-skip-forward:before{content:'\e096'}.oi-media-step-backward:before{content:'\e097'}.oi-media-step-forward:before{content:'\e098'}.oi-media-stop:before{content:'\e099'}.oi-medical-cross:before{content:'\e09a'}.oi-menu:before{content:'\e09b'}.oi-microphone:before{content:'\e09c'}.oi-minus:before{content:'\e09d'}.oi-monitor:before{content:'\e09e'}.oi-moon:before{content:'\e09f'}.oi-move:before{content:'\e0a0'}.oi-musical-note:before{content:'\e0a1'}.oi-paperclip:before{content:'\e0a2'}.oi-pencil:before{content:'\e0a3'}.oi-people:before{content:'\e0a4'}.oi-person:before{content:'\e0a5'}.oi-phone:before{content:'\e0a6'}.oi-pie-chart:before{content:'\e0a7'}.oi-pin:before{content:'\e0a8'}.oi-play-circle:before{content:'\e0a9'}.oi-plus:before{content:'\e0aa'}.oi-power-standby:before{content:'\e0ab'}.oi-print:before{content:'\e0ac'}.oi-project:before{content:'\e0ad'}.oi-pulse:before{content:'\e0ae'}.oi-puzzle-piece:before{content:'\e0af'}.oi-question-mark:before{content:'\e0b0'}.oi-rain:before{content:'\e0b1'}.oi-random:before{content:'\e0b2'}.oi-reload:before{content:'\e0b3'}.oi-resize-both:before{content:'\e0b4'}.oi-resize-height:before{content:'\e0b5'}.oi-resize-width:before{content:'\e0b6'}.oi-rss-alt:before{content:'\e0b7'}.oi-rss:before{content:'\e0b8'}.oi-script:before{content:'\e0b9'}.oi-share-boxed:before{content:'\e0ba'}.oi-share:before{content:'\e0bb'}.oi-shield:before{content:'\e0bc'}.oi-signal:before{content:'\e0bd'}.oi-signpost:before{content:'\e0be'}.oi-sort-ascending:before{content:'\e0bf'}.oi-sort-descending:before{content:'\e0c0'}.oi-spreadsheet:before{content:'\e0c1'}.oi-star:before{content:'\e0c2'}.oi-sun:before{content:'\e0c3'}.oi-tablet:before{content:'\e0c4'}.oi-tag:before{content:'\e0c5'}.oi-tags:before{content:'\e0c6'}.oi-target:before{content:'\e0c7'}.oi-task:before{content:'\e0c8'}.oi-terminal:before{content:'\e0c9'}.oi-text:before{content:'\e0ca'}.oi-thumb-down:before{content:'\e0cb'}.oi-thumb-up:before{content:'\e0cc'}.oi-timer:before{content:'\e0cd'}.oi-transfer:before{content:'\e0ce'}.oi-trash:before{content:'\e0cf'}.oi-underline:before{content:'\e0d0'}.oi-vertical-align-bottom:before{content:'\e0d1'}.oi-vertical-align-center:before{content:'\e0d2'}.oi-vertical-align-top:before{content:'\e0d3'}.oi-video:before{content:'\e0d4'}.oi-volume-high:before{content:'\e0d5'}.oi-volume-low:before{content:'\e0d6'}.oi-volume-off:before{content:'\e0d7'}.oi-warning:before{content:'\e0d8'}.oi-wifi:before{content:'\e0d9'}.oi-wrench:before{content:'\e0da'}.oi-x:before{content:'\e0db'}.oi-yen:before{content:'\e0dc'}.oi-zoom-in:before{content:'\e0dd'}.oi-zoom-out:before{content:'\e0de'} -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j4587698/BlazorLearn/e6c2de497a83b93a21165f24d97b9dc10a3fd291/BlazorLearn/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j4587698/BlazorLearn/e6c2de497a83b93a21165f24d97b9dc10a3fd291/BlazorLearn/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/css/open-iconic/font/fonts/open-iconic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by FontForge 20120731 at Tue Jul 1 20:39:22 2014 9 | By P.J. Onori 10 | Created by P.J. Onori with FontForge 2.0 (http://fontforge.sf.net) 11 | 12 | 13 | 14 | 27 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 45 | 47 | 49 | 51 | 53 | 55 | 57 | 59 | 61 | 63 | 65 | 67 | 69 | 71 | 74 | 76 | 79 | 81 | 84 | 86 | 88 | 91 | 93 | 95 | 98 | 100 | 102 | 104 | 106 | 109 | 112 | 115 | 117 | 121 | 123 | 125 | 127 | 130 | 132 | 134 | 136 | 138 | 141 | 143 | 145 | 147 | 149 | 151 | 153 | 155 | 157 | 159 | 162 | 165 | 167 | 169 | 172 | 174 | 177 | 179 | 181 | 183 | 185 | 189 | 191 | 194 | 196 | 198 | 200 | 202 | 205 | 207 | 209 | 211 | 213 | 215 | 218 | 220 | 222 | 224 | 226 | 228 | 230 | 232 | 234 | 236 | 238 | 241 | 243 | 245 | 247 | 249 | 251 | 253 | 256 | 259 | 261 | 263 | 265 | 267 | 269 | 272 | 274 | 276 | 280 | 282 | 285 | 287 | 289 | 292 | 295 | 298 | 300 | 302 | 304 | 306 | 309 | 312 | 314 | 316 | 318 | 320 | 322 | 324 | 326 | 330 | 334 | 338 | 340 | 343 | 345 | 347 | 349 | 351 | 353 | 355 | 358 | 360 | 363 | 365 | 367 | 369 | 371 | 373 | 375 | 377 | 379 | 381 | 383 | 386 | 388 | 390 | 392 | 394 | 396 | 399 | 401 | 404 | 406 | 408 | 410 | 412 | 414 | 416 | 419 | 421 | 423 | 425 | 428 | 431 | 435 | 438 | 440 | 442 | 444 | 446 | 448 | 451 | 453 | 455 | 457 | 460 | 462 | 464 | 466 | 468 | 471 | 473 | 477 | 479 | 481 | 483 | 486 | 488 | 490 | 492 | 494 | 496 | 499 | 501 | 504 | 506 | 509 | 512 | 515 | 517 | 520 | 522 | 524 | 526 | 529 | 532 | 534 | 536 | 539 | 542 | 543 | 544 | -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j4587698/BlazorLearn/e6c2de497a83b93a21165f24d97b9dc10a3fd291/BlazorLearn/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j4587698/BlazorLearn/e6c2de497a83b93a21165f24d97b9dc10a3fd291/BlazorLearn/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); 2 | 3 | html, body { 4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | } 6 | 7 | h1:focus { 8 | outline: none; 9 | } 10 | 11 | a, .btn-link { 12 | color: #0071c1; 13 | } 14 | 15 | .btn-primary { 16 | color: #fff; 17 | background-color: #1b6ec2; 18 | border-color: #1861ac; 19 | } 20 | 21 | .content { 22 | padding-top: 1.1rem; 23 | } 24 | 25 | .valid.modified:not([type=checkbox]) { 26 | outline: 1px solid #26b050; 27 | } 28 | 29 | .invalid { 30 | outline: 1px solid red; 31 | } 32 | 33 | .validation-message { 34 | color: red; 35 | } 36 | 37 | #blazor-error-ui { 38 | background: lightyellow; 39 | bottom: 0; 40 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 41 | display: none; 42 | left: 0; 43 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 44 | position: fixed; 45 | width: 100%; 46 | z-index: 1000; 47 | } 48 | 49 | #blazor-error-ui .dismiss { 50 | cursor: pointer; 51 | position: absolute; 52 | right: 0.75rem; 53 | top: 0.5rem; 54 | } 55 | 56 | .blazor-error-boundary { 57 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 58 | padding: 1rem 1rem 1rem 3.7rem; 59 | color: white; 60 | } 61 | 62 | .blazor-error-boundary::after { 63 | content: "An error has occurred." 64 | } 65 | -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j4587698/BlazorLearn/e6c2de497a83b93a21165f24d97b9dc10a3fd291/BlazorLearn/wwwroot/favicon.ico -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/images/Argo-C.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j4587698/BlazorLearn/e6c2de497a83b93a21165f24d97b9dc10a3fd291/BlazorLearn/wwwroot/images/Argo-C.png -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/images/Argo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j4587698/BlazorLearn/e6c2de497a83b93a21165f24d97b9dc10a3fd291/BlazorLearn/wwwroot/images/Argo.png -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/images/bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j4587698/BlazorLearn/e6c2de497a83b93a21165f24d97b9dc10a3fd291/BlazorLearn/wwwroot/images/bg2.jpg -------------------------------------------------------------------------------- /BlazorLearn/wwwroot/images/bg3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j4587698/BlazorLearn/e6c2de497a83b93a21165f24d97b9dc10a3fd291/BlazorLearn/wwwroot/images/bg3.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BlazorLearn 2 | 3 | Blazor Server示例程序 4 | 5 | 对接博客园[blazor server 从零开始](https://www.cnblogs.com/j4587698/category/2195150.html)系列文章。 6 | --------------------------------------------------------------------------------