diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Layout/BreadcrumbItem.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Layout/BreadcrumbItem.cs
new file mode 100644
index 0000000000..f2c8075707
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Layout/BreadcrumbItem.cs
@@ -0,0 +1,18 @@
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Layout
+{
+ public class BreadcrumbItem
+ {
+ public string Text { get; set; }
+
+ public object Icon { get; set; }
+
+ public string Url { get; set; }
+
+ public BreadcrumbItem(string text, string url = null, object icon = null)
+ {
+ Text = text;
+ Url = url;
+ Icon = icon;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Layout/PageHeader.razor b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Layout/PageHeader.razor
new file mode 100644
index 0000000000..df8f009290
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Layout/PageHeader.razor
@@ -0,0 +1,42 @@
+@using Blazorise
+@using BlazoriseUI
+
+
+
+
+ @if (BreadcrumbItems.Any())
+ {
+
+
+ @if (BreadcrumbShowHome)
+ {
+
+
+
+
+
+ }
+ @foreach (var item in BreadcrumbItems)
+ {
+
+
+ @if (item.Icon != null)
+ {
+
+ }
+ @item.Text
+
+
+ }
+
+
+ }
+
+
+ @foreach (var toolbarItemRender in ToolbarItemRenders)
+ {
+ @toolbarItemRender
+ }
+
+
+
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Layout/PageHeader.razor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Layout/PageHeader.razor.cs
new file mode 100644
index 0000000000..60d8d51b42
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/Layout/PageHeader.razor.cs
@@ -0,0 +1,69 @@
+using Microsoft.AspNetCore.Components;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Volo.Abp.AspNetCore.Components.WebAssembly.Theming.PageToolbars;
+
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.Layout
+{
+ public partial class PageHeader : ComponentBase
+ {
+ protected List ToolbarItemRenders { get; set; }
+
+ public IPageToolbarManager PageToolbarManager { get; set; }
+
+ [Parameter]
+ public string Title { get; set; }
+
+ [Parameter]
+ public bool BreadcrumbShowHome { get; set; } = true;
+
+ [Parameter]
+ public bool BreadcrumbShowCurrent { get; set; } = true;
+
+ [Parameter]
+ public RenderFragment ChildContent { get; set; }
+
+ [Parameter]
+ public List BreadcrumbItems { get; set; }
+
+ [Parameter]
+ public string PageName { get; set; }
+
+
+ public PageHeader(IPageToolbarManager pageToolbarManager)
+ {
+ BreadcrumbItems = new List();
+ ToolbarItemRenders = new List();
+ PageToolbarManager = pageToolbarManager;
+ }
+
+ protected override async Task OnInitializedAsync()
+ {
+ if (!PageName.IsNullOrEmpty())
+ {
+ var toolbarItems = await PageToolbarManager.GetItemsAsync(PageName);
+
+ ToolbarItemRenders.Clear();
+
+ foreach (var item in toolbarItems)
+ {
+ var sequence = 0;
+ ToolbarItemRenders.Add(builder =>
+ {
+ builder.OpenComponent(sequence, item.ComponentType);
+ if (item.Arguments != null)
+ {
+ foreach (var argument in item.Arguments)
+ {
+ sequence++;
+ builder.AddAttribute(sequence, argument.Key, argument.Value);
+ }
+ }
+ builder.CloseComponent();
+ });
+ }
+ }
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/AbpPageToolbarOptions.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/AbpPageToolbarOptions.cs
new file mode 100644
index 0000000000..7bfcf5acaf
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/AbpPageToolbarOptions.cs
@@ -0,0 +1,30 @@
+using JetBrains.Annotations;
+using System;
+using System.Collections.Generic;
+
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.PageToolbars
+{
+ public class AbpPageToolbarOptions
+ {
+ public PageToolbarDictionary Toolbars { get; }
+
+ public AbpPageToolbarOptions()
+ {
+ Toolbars = new PageToolbarDictionary();
+ }
+
+ public void Configure([NotNull] Action configureAction)
+ {
+ Configure(typeof(TPage).FullName, configureAction);
+ }
+
+ public void Configure([NotNull] string pageName, [NotNull] Action configureAction)
+ {
+ Check.NotNullOrWhiteSpace(pageName, nameof(pageName));
+ Check.NotNull(configureAction, nameof(configureAction));
+
+ var toolbar = Toolbars.GetOrAdd(pageName, () => new PageToolbar(pageName));
+ configureAction(toolbar);
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/IPageToolbarContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/IPageToolbarContributor.cs
new file mode 100644
index 0000000000..b90fdfd768
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/IPageToolbarContributor.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.PageToolbars
+{
+ public interface IPageToolbarContributor
+ {
+ Task ContributeAsync(PageToolbarContributionContext context);
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/IPageToolbarManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/IPageToolbarManager.cs
new file mode 100644
index 0000000000..9344afe91f
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/IPageToolbarManager.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.PageToolbars
+{
+ public interface IPageToolbarManager
+ {
+ Task GetItemsAsync(string pageName);
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbar.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbar.cs
new file mode 100644
index 0000000000..8f7e5a393c
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbar.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.PageToolbars
+{
+ public class PageToolbar
+ {
+ public string PageName { get; }
+
+ public PageToolbarContributorList Contributors { get; set; }
+
+ public PageToolbar([NotNull] string pageName)
+ {
+ PageName = Check.NotNullOrEmpty(pageName, nameof(pageName));
+ Contributors = new PageToolbarContributorList();
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarContributionContext.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarContributionContext.cs
new file mode 100644
index 0000000000..3cae80199b
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarContributionContext.cs
@@ -0,0 +1,27 @@
+using JetBrains.Annotations;
+using System;
+
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.PageToolbars
+{
+ public class PageToolbarContributionContext
+ {
+ [NotNull]
+ public string PageName { get; }
+
+ [NotNull]
+ public IServiceProvider ServiceProvider { get; }
+
+ [NotNull]
+ public PageToolbarItemList Items { get; }
+
+ public PageToolbarContributionContext(
+ [NotNull] string pageName,
+ [NotNull] IServiceProvider serviceProvider)
+ {
+ PageName = Check.NotNull(pageName, nameof(pageName));
+ ServiceProvider = Check.NotNull(serviceProvider, nameof(serviceProvider));
+
+ Items = new PageToolbarItemList();
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarContributor.cs
new file mode 100644
index 0000000000..ee452407ce
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarContributor.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.PageToolbars
+{
+ public abstract class PageToolbarContributor : IPageToolbarContributor
+ {
+ public abstract Task ContributeAsync(PageToolbarContributionContext context);
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarContributorList.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarContributorList.cs
new file mode 100644
index 0000000000..b3c2157b3b
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarContributorList.cs
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.PageToolbars
+{
+ public class PageToolbarContributorList : List
+ {
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarDictionary.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarDictionary.cs
new file mode 100644
index 0000000000..c239bc52f4
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarDictionary.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.PageToolbars
+{
+ public class PageToolbarDictionary : Dictionary
+ {
+
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarExtensions.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarExtensions.cs
new file mode 100644
index 0000000000..71ff4b3530
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarExtensions.cs
@@ -0,0 +1,71 @@
+using Blazorise;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Volo.Abp.BlazoriseUI.Components;
+using Volo.Abp.Localization;
+
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.PageToolbars
+{
+ public static class PageToolbarExtensions
+ {
+ public static PageToolbar AddComponent(
+ this PageToolbar toolbar,
+ Dictionary arguments = null,
+ int order = 0,
+ string requiredPolicyName = null)
+ {
+ return toolbar.AddComponent(
+ typeof(TComponent),
+ arguments,
+ order,
+ requiredPolicyName
+ );
+ }
+
+ public static PageToolbar AddComponent(
+ this PageToolbar toolbar,
+ Type componentType,
+ Dictionary arguments = null,
+ int order = 0,
+ string requiredPolicyName = null)
+ {
+ toolbar.Contributors.Add(
+ new SimplePageToolbarContributor(
+ componentType,
+ arguments,
+ order,
+ requiredPolicyName
+ )
+ );
+
+ return toolbar;
+ }
+
+ public static PageToolbar AddButton(
+ this PageToolbar toolbar,
+ string text,
+ Func clicked,
+ object icon = null,
+ Color color = Color.Primary,
+ bool disabled = false,
+ int order = 0,
+ string requiredPolicyName = null)
+ {
+ toolbar.AddComponent(
+ new Dictionary
+ {
+ { nameof(ToolbarButton.Color), color},
+ { nameof(ToolbarButton.Text), text},
+ { nameof(ToolbarButton.Disabled), disabled},
+ { nameof(ToolbarButton.Icon), icon},
+ { nameof(ToolbarButton.Clicked),clicked},
+ },
+ order,
+ requiredPolicyName
+ );
+
+ return toolbar;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarItem.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarItem.cs
new file mode 100644
index 0000000000..dd940fc84f
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarItem.cs
@@ -0,0 +1,27 @@
+using JetBrains.Annotations;
+using System;
+using System.Collections.Generic;
+
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.PageToolbars
+{
+ public class PageToolbarItem
+ {
+ [NotNull]
+ public Type ComponentType { get; }
+
+ [CanBeNull]
+ public Dictionary Arguments { get; set; }
+
+ public int Order { get; set; }
+
+ public PageToolbarItem(
+ [NotNull] Type componentType,
+ [CanBeNull] Dictionary arguments = null,
+ int order = 0)
+ {
+ ComponentType = Check.NotNull(componentType, nameof(componentType));
+ Arguments = arguments;
+ Order = order;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarItemList.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarItemList.cs
new file mode 100644
index 0000000000..e427b62df2
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarItemList.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.PageToolbars
+{
+ public class PageToolbarItemList : List
+ {
+
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarManager.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarManager.cs
new file mode 100644
index 0000000000..f8eb784ebf
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/PageToolbarManager.cs
@@ -0,0 +1,44 @@
+using Microsoft.Extensions.Options;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.PageToolbars
+{
+ public class PageToolbarManager : IPageToolbarManager, ITransientDependency
+ {
+ protected AbpPageToolbarOptions Options { get; }
+ protected IHybridServiceScopeFactory ServiceScopeFactory { get; }
+
+ public PageToolbarManager(
+ IOptions options,
+ IHybridServiceScopeFactory serviceScopeFactory)
+ {
+ Options = options.Value;
+ ServiceScopeFactory = serviceScopeFactory;
+ }
+
+ public virtual async Task GetItemsAsync(string pageName)
+ {
+ var toolbar = Options.Toolbars.GetOrDefault(pageName);
+ if (toolbar == null || !toolbar.Contributors.Any())
+ {
+ return Array.Empty();
+ }
+
+ using (var scope = ServiceScopeFactory.CreateScope())
+ {
+ var context = new PageToolbarContributionContext(pageName, scope.ServiceProvider);
+
+ foreach (var contributor in toolbar.Contributors)
+ {
+ await contributor.ContributeAsync(context);
+ }
+
+ return context.Items.OrderBy(i => i.Order).ToArray();
+ }
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/SimplePageToolbarContributor.cs b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/SimplePageToolbarContributor.cs
new file mode 100644
index 0000000000..e138faaae1
--- /dev/null
+++ b/framework/src/Volo.Abp.AspNetCore.Components.WebAssembly.Theming/PageToolbars/SimplePageToolbarContributor.cs
@@ -0,0 +1,53 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Volo.Abp.AspNetCore.Components.WebAssembly.Theming.PageToolbars
+{
+ public class SimplePageToolbarContributor : IPageToolbarContributor
+ {
+ public Type ComponentType { get; }
+
+ public Dictionary Arguments { get; set; }
+
+ public int Order { get; }
+
+ public string RequiredPolicyName { get; }
+
+ public SimplePageToolbarContributor(
+ Type componentType,
+ Dictionary arguments = null,
+ int order = 0,
+ string requiredPolicyName = null)
+ {
+ ComponentType = componentType;
+ Arguments = arguments;
+ Order = order;
+ RequiredPolicyName = requiredPolicyName;
+ }
+
+ public async Task ContributeAsync(PageToolbarContributionContext context)
+ {
+ if (await ShouldAddComponentAsync(context))
+ {
+ context.Items.Add(new PageToolbarItem(ComponentType, Arguments, Order));
+ }
+ }
+
+ protected virtual async Task ShouldAddComponentAsync(PageToolbarContributionContext context)
+ {
+ if (RequiredPolicyName != null)
+ {
+ var authorizationService = context.ServiceProvider.GetRequiredService();
+ if (!await authorizationService.IsGrantedAsync(RequiredPolicyName))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs
index 04aecf1f5f..59070f32b0 100644
--- a/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs
+++ b/framework/src/Volo.Abp.BlazoriseUI/AbpCrudPageBase.cs
@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
+using Microsoft.Extensions.Options;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.AspNetCore.Components;
@@ -206,6 +207,7 @@ namespace Volo.Abp.BlazoriseUI
protected override async Task OnInitializedAsync()
{
+ await SetToolbarItemsAsync();
await SetEntityActionsAsync();
await SetTableColumnsAsync();
await SetBreadcrumbItemsAsync();
@@ -462,5 +464,10 @@ namespace Volo.Abp.BlazoriseUI
{
return ValueTask.CompletedTask;
}
+
+ protected virtual ValueTask SetToolbarItemsAsync()
+ {
+ return ValueTask.CompletedTask;
+ }
}
}
diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ToolbarButton.razor b/framework/src/Volo.Abp.BlazoriseUI/Components/ToolbarButton.razor
new file mode 100644
index 0000000000..0cd13de50d
--- /dev/null
+++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ToolbarButton.razor
@@ -0,0 +1,7 @@
+
diff --git a/framework/src/Volo.Abp.BlazoriseUI/Components/ToolbarButton.razor.cs b/framework/src/Volo.Abp.BlazoriseUI/Components/ToolbarButton.razor.cs
new file mode 100644
index 0000000000..bdfc068833
--- /dev/null
+++ b/framework/src/Volo.Abp.BlazoriseUI/Components/ToolbarButton.razor.cs
@@ -0,0 +1,25 @@
+using Blazorise;
+using Microsoft.AspNetCore.Components;
+using System;
+using System.Threading.Tasks;
+
+namespace Volo.Abp.BlazoriseUI.Components
+{
+ public partial class ToolbarButton : ComponentBase
+ {
+ [Parameter]
+ public Color Color { get; set; }
+
+ [Parameter]
+ public object Icon { get; set; }
+
+ [Parameter]
+ public string Text { get; set; }
+
+ [Parameter]
+ public Func Clicked { get; set; }
+
+ [Parameter]
+ public bool Disabled { get; set; }
+ }
+}