diff --git a/src/BootstrapBlazor.Shared/Samples/LayoutPages.razor.cs b/src/BootstrapBlazor.Shared/Samples/LayoutPages.razor.cs index f83f6a02c1905604cf564cdcb0e8a99584398272..690141794a115b83fb34e846279eda4843ea5197 100644 --- a/src/BootstrapBlazor.Shared/Samples/LayoutPages.razor.cs +++ b/src/BootstrapBlazor.Shared/Samples/LayoutPages.razor.cs @@ -5,6 +5,7 @@ using BootstrapBlazor.Components; using BootstrapBlazor.Shared.Shared; using Microsoft.AspNetCore.Components; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -122,6 +123,10 @@ namespace BootstrapBlazor.Shared.Samples RootPage.Update(); } + [Inject] + [NotNull] + private IServiceProvider? Provider { get; set; } + private Task OnNavigation() { Navigator.NavigateTo("layout-page1", "代码导航"); diff --git a/src/BootstrapBlazor.Shared/Samples/Uploads.razor b/src/BootstrapBlazor.Shared/Samples/Uploads.razor index fd922f7698b151e20fd454be7d9e3f8a2733a078..4cab94c44c92f9aab7b17338be7c1e9454edce17 100644 --- a/src/BootstrapBlazor.Shared/Samples/Uploads.razor +++ b/src/BootstrapBlazor.Shared/Samples/Uploads.razor @@ -1,31 +1,31 @@ @page "/uploads" -

@Localizer["Title"]

+

Upload 上传

-

@Localizer["SubTitle"]

+

通过点击上传文件

- +
- +
- +
- +
- +
    -
  • @((MarkupString)Localizer["FormLi1"].Value)
  • -
  • @((MarkupString)Localizer["FormLi2"].Value)
  • +
  • 使用 ValidateForm 表单组件,通过设置模型属性的 FileValidation 标签设置自定义验证,支持文件 扩展名 大小 验证,本例中设置扩展名为 .png .jpg .jpeg,文件大小限制为 50K
  • +
  • 选择文件后并未开始上传文件,点击 提交 按钮数据验证合法后,再 OnSubmit 回调委托中进行上传文件操作,注意 Picture 属性类型为 IBrowserFile
@@ -36,24 +36,24 @@
- +
- +
-

@((MarkupString)Localizer["ClickUploadP1"].Value)

+

点击 浏览按钮 选择文件上传,本例中设置 IsMultiple=true 可多选文件进行上传

-

@((MarkupString)Localizer["ClickUploadP2"].Value)

+

设置 IsSingle 时,仅可以上传一张图片或者文件

- +
@@ -61,7 +61,7 @@
- +
@@ -69,30 +69,32 @@
- +
-

@Localizer["AvatarUploadP1"]

+

卡片形式头像框

-

@Localizer["AvatarUploadP2"]

+

圆形头像框

-

@((MarkupString)Localizer["AvatarUploadP3"].Value)

+

设置 IsSingle 时,仅可以上传一张图片或者文件

-

@((MarkupString)Localizer["AvatarUploadP5"].Value)

-

+

+

组件提供了 Accept 属性用于设置上传文件过滤功能,本例中圆形头像框接受 GIF 和 JPEG 两种图像,设置 Accept="image/gif, image/jpeg",如果不限制图像的格式,可以写为:Accept="image/*",该属性并不安全还是应该是使用 服务器端验证 进行文件格式验证
+

+

相关文档:[Accept 属性详解] [Media Types 详细列表]

-

@((MarkupString)Localizer["AvatarUploadP6"].Value)

+

通过 DefaultFileList 属性设置预览地址 PrevUrl 即可

-

@((MarkupString)Localizer["AvatarUploadP7"].Value)

+

验证表单内使用头像框示例

@@ -102,7 +104,7 @@
- +
@@ -111,29 +113,29 @@ - +

-

@((MarkupString)Localizer["PreCardStyleSSR"].Value)
-
@((MarkupString)Localizer["PreCardStyleServerSide"].Value)
+
SSR 模式
+
Server Side 模式中可以使用 IWebHostEnvironment 注入服务获取到 wwwwroot 目录,保存文件到 images\uploader 中,此功能无需 MVC 的控制器辅助进行文件的保存,直接调用 SaveToFile 方法即可
-
@((MarkupString)Localizer["PreCardStyleWasm"].Value)
-
@((MarkupString)Localizer["PreCardStyleWasmSide"].Value)
-
@((MarkupString)Localizer["PreCardStyleLink", SiteOptions.Value.VideoLibUrl].Value)
-
@((MarkupString)Localizer["PreCardStyleValidation"].Value)
+
Wasm 模式
+
wasm 模式中无法使用 IWebHostEnvironment 需要调用 webapi 接口等形式将文件保存到服务器端
+
有兴趣的同学可以通过开源仓库中的 wiki 文档中相关资源查看关于 Upload 组件的相关知识技巧 [传送门]
+
本例中通过服务器端验证当文件大小超过 200MB 时,提示文件太大提示信息

-

@((MarkupString)Localizer["PreCardStyleP1"].Value)

+

本例中设置 ShowProgress=true 显示上传进度条

-

@((MarkupString)Localizer["PreCardStyleP2"].Value)

+

设置 IsSingle 时,仅可以上传一张图片或者文件

- + diff --git a/src/BootstrapBlazor.Shared/wwwroot/css/motronic.css b/src/BootstrapBlazor.Shared/wwwroot/css/motronic.css index 8c838afb164c672b52aea8f426df5e99f7382e5a..2072eb54ed6bf9a50f55e14fd88cd9139c3af106 100644 --- a/src/BootstrapBlazor.Shared/wwwroot/css/motronic.css +++ b/src/BootstrapBlazor.Shared/wwwroot/css/motronic.css @@ -21,15 +21,18 @@ } .tabs-body { - background-color: #f5f8fa; + background-color: #fff; } .tabs-body-content { border-radius: var(--bs-border-radius); padding: 1rem; - background-color: #fff; } + .tabs-body-content .tabs-body-content { + background-color: #f5f8fa; + } + .layout.is-page .layout-footer { background-color: #e9ecef; } diff --git a/src/BootstrapBlazor/Components/Tab/Tab.razor.cs b/src/BootstrapBlazor/Components/Tab/Tab.razor.cs index 6a86f0d4f28724fbf8f9d6f561ef6c6cb021f2ac..bfed0d40e4c5697cb653d2d8c16dbac018de4ba5 100644 --- a/src/BootstrapBlazor/Components/Tab/Tab.razor.cs +++ b/src/BootstrapBlazor/Components/Tab/Tab.razor.cs @@ -458,8 +458,12 @@ namespace BootstrapBlazor.Components /// public void AddTab(string url, string text, string? icon = null, bool active = true, bool closable = true) { - AddTabItem(url, text, icon, active, closable); + Options.Text = text; + Options.Icon = icon; + Options.IsActive = active; + Options.Closable = closable; + AddTabItem(url); StateHasChanged(); } @@ -469,24 +473,28 @@ namespace BootstrapBlazor.Components return TabItemTextDictionary != null && TabItemTextDictionary.TryGetValue(url, out text); } - private void AddTabItem(string url, string? text = null, string? icon = null, bool? active = null, bool? closable = null) + private void AddTabItem(string url) { var parameters = new Dictionary(); var context = RouteTableFactory.Create(AdditionalAssemblies, url); if (context.Handler != null) { - var option = context.Handler.GetCustomAttribute(false); - if (option != null) + // 检查 Options 优先 + if (Options.Valid()) { - parameters.Add(nameof(TabItem.Icon), option.Icon); - parameters.Add(nameof(TabItem.Closable), option.Closable); - parameters.Add(nameof(TabItem.Text), option.Text); - parameters.Add(nameof(TabItem.IsActive), active ?? true); - parameters.Add(nameof(TabItem.Url), url); + AddParameters(); } else { - AddParameters(); + var option = context.Handler.GetCustomAttribute(false); + if (option != null) + { + parameters.Add(nameof(TabItem.Icon), option.Icon); + parameters.Add(nameof(TabItem.Closable), option.Closable); + parameters.Add(nameof(TabItem.Text), option.Text); + parameters.Add(nameof(TabItem.IsActive), true); + parameters.Add(nameof(TabItem.Url), url); + } } parameters.Add(nameof(TabItem.ChildContent), new RenderFragment(builder => @@ -499,7 +507,7 @@ namespace BootstrapBlazor.Components } else { - parameters.Add(nameof(TabItem.Text), text ?? NotFoundTabText); + parameters.Add(nameof(TabItem.Text), NotFoundTabText); parameters.Add(nameof(TabItem.ChildContent), new RenderFragment(builder => { builder.AddContent(0, NotFound); @@ -510,17 +518,17 @@ namespace BootstrapBlazor.Components void AddParameters() { - if (TryGetTabItemText(url, out var tabText)) + var text = Options.Text; + var icon = Options.Icon ?? string.Empty; + var active = Options.IsActive ?? true; + var closable = Options.Closable ?? true; + Options.Reset(); + + if (string.IsNullOrEmpty(text) && TryGetTabItemText(url, out var tabText)) { text = tabText; } - text ??= Options.Text; - icon ??= Options.Icon ?? string.Empty; - active ??= Options.IsActive ?? true; - closable ??= Options.Closable ?? true; - Options.Reset(); - parameters.Add(nameof(TabItem.Url), url); parameters.Add(nameof(TabItem.Icon), icon); parameters.Add(nameof(TabItem.Closable), closable); diff --git a/src/BootstrapBlazor/Extensions/JSRuntimeExtensions.cs b/src/BootstrapBlazor/Extensions/JSRuntimeExtensions.cs index 6036bbfc320dd829bd0c754180d9a87074101c5a..39831c03a44faa658a9af1deb21218bfbfd00ffd 100644 --- a/src/BootstrapBlazor/Extensions/JSRuntimeExtensions.cs +++ b/src/BootstrapBlazor/Extensions/JSRuntimeExtensions.cs @@ -38,6 +38,10 @@ namespace BootstrapBlazor.Components { await jsRuntime.InvokeVoidAsync($"$.{func}", paras.ToArray()).ConfigureAwait(false); } +#if NET6_0_OR_GREATER + catch (JSDisconnectedException) { } +#endif + catch (AggregateException) { } catch (InvalidOperationException) { } catch (TaskCanceledException) { } } @@ -67,6 +71,10 @@ namespace BootstrapBlazor.Components { ret = await jsRuntime.InvokeAsync($"$.{func}", paras.ToArray()).ConfigureAwait(false); } +#if NET6_0_OR_GREATER + catch (JSDisconnectedException) { } +#endif + catch (AggregateException) { } catch (InvalidOperationException) { } catch (TaskCanceledException) { } return ret; diff --git a/src/BootstrapBlazor/Extensions/NavigationManagerExtensions.cs b/src/BootstrapBlazor/Extensions/NavigationManagerExtensions.cs index d8318cc0d9625d11ac3f34c3341268863c183149..c1dcef766b8e85bf3a54f53bb43825ebdc10b363 100644 --- a/src/BootstrapBlazor/Extensions/NavigationManagerExtensions.cs +++ b/src/BootstrapBlazor/Extensions/NavigationManagerExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; +using System; namespace BootstrapBlazor.Components { @@ -22,7 +23,7 @@ namespace BootstrapBlazor.Components /// public static void NavigateTo(this NavigationManager navigation, string url, string text, string? icon = null, bool? closable = null) { - var option = ServiceProviderFactory.Services.GetRequiredService(); + var option = ServiceProviderFactory.ScopeServices.GetRequiredService(); option.Text = text; option.Icon = icon; option.IsActive = true; diff --git a/src/BootstrapBlazor/Options/TabItemTextOptions.cs b/src/BootstrapBlazor/Options/TabItemTextOptions.cs index 4ea62e8401c0e443de11945f9eb38b2dc0a00cab..812dba15bb05c594eba0ca2fc2413dcd6719ec06 100644 --- a/src/BootstrapBlazor/Options/TabItemTextOptions.cs +++ b/src/BootstrapBlazor/Options/TabItemTextOptions.cs @@ -30,8 +30,9 @@ namespace BootstrapBlazor.Components /// public bool? Closable { get; set; } - - + /// + /// + /// public void Reset() { Text = null; @@ -39,5 +40,11 @@ namespace BootstrapBlazor.Components IsActive = null; Closable = null; } + + /// + /// + /// + /// + public bool Valid() => Text != null; } } diff --git a/src/BootstrapBlazor/Utils/ServiceProviderFactory.cs b/src/BootstrapBlazor/Utils/ServiceProviderFactory.cs index 79722c04662d313dc09a99be4a8dccac54c9b7b7..f7bb09c64c177af78045df04816fac44a052bcec 100644 --- a/src/BootstrapBlazor/Utils/ServiceProviderFactory.cs +++ b/src/BootstrapBlazor/Utils/ServiceProviderFactory.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Website: https://www.blazor.zone or https://argozhang.github.io/ +using Microsoft.Extensions.DependencyInjection; using System; using System.Diagnostics.CodeAnalysis; @@ -15,11 +16,16 @@ namespace BootstrapBlazor.Components private static IServiceProvider? _provider; /// - /// 获取系统 IServiceProvider 接口 + /// 获取系统 Root IServiceProvider 接口 /// [NotNull] public static IServiceProvider? Services => _provider ?? throw new InvalidOperationException($"{nameof(ServiceProviderFactory.Services)} is null"); + /// + /// 获取当前 Scope IServiceProvider 接口 + /// + public static IServiceProvider ScopeServices => Services.CreateScope().ServiceProvider; + /// /// ///