18 KiB
仪表板和小部件(Widget)系统
仪表板和小部件(Widget)系统允许你创建可重用的小部件和仪表板。
你可以在上面的屏幕截图中看到使用该系统构建的示例仪表板. 放置过滤器和刷新按钮的顶部是全局过滤器部分,下面的每个卡片都是一个小部件. 小部件和全局过滤器都是可重用的组件.仪表板布局也可以重复使用.
现在我们将看到如何在应用程序中使用它们.
仪表板组件
首先,我们将在应用程序中定义仪表板,你可以从abp.io/get-started下载新的应用程序模板. 为简单起见,请不要使用分层选项.
在 *.Web项目中, 我们创建DashboardNames.cs和DashboardDefinitionProvider.cs类:
public static class DashboardNames
{
public const string MyDashboard = "MyDashboard";
public static string[] GetAll()
{
return ReflectionHelper.GetPublicConstantsRecursively(typeof(DashboardNames));
}
}
public static class DashboardDefinitionProvider
{
public static List<DashboardDefinition> GetDefinitions()
{
var myDashboard = new DashboardDefinition(
DashboardNames.MyDashboard,
LocalizableString.Create<DashboardDemoResource>("MyDashboard")
);
return new List<DashboardDefinition>
{
myDashboard
};
}
}
我们需要将该定义添加到WebModule.cs文件中ConfigureServices方法的DashboardOptions:
using Volo.Abp.AspNetCore.Mvc.UI.Dashboards;
//...
public class DashboardDemoWebModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
//other codes..
Configure<DashboardOptions>(options =>
{
options.Dashboards.AddRange(DashboardDefinitionProvider.GetDefinitions());
})
//other codes..
}
//other codes..
}
然后我们可以创建我们定义的仪表板它将被渲染 Pages/MyDashboard.cshtml:
@page
@using DashboardDemo.Dashboards
@using Microsoft.Extensions.Localization
@using Volo.Abp.AspNetCore.Mvc.UI.Dashboards
@using Volo.Abp.AspNetCore.Mvc.UI.Widgets
@inject IWidgetRenderer WidgetRenderer
@inject IDashboardRenderer DashboardRenderer
@inject IStringLocalizerFactory localizer
@model DashboardDemo.Pages.MyDashboardModel
@{
}
@section styles {
<abp-style-bundle name="@DashboardNames.MyDashboard" />
}
@section scripts {
<abp-script-bundle name="@DashboardNames.MyDashboard" />
}
@await DashboardRenderer.RenderAsync(Component, new { dashboardName = DashboardNames.MyDashboard })
DashboardRenderer.RenderAsync 方法呈现我们定义的仪表板. 现在我们必须定义script和style bundles. 你可以在上面的代码中看到它们的用法:
[DependsOn(typeof(AbpBasicDashboardStyleContributor))]
public class MyDashboardStyleBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
}
}
[DependsOn(typeof(AbpBasicDashboardScriptContributor))]
public class MyDashboardScriptBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
}
}
仪表板系统使用Bundling & Minification作为脚本和样式. 仪表板贡献者将依赖于他们的小部件和全局过滤器贡献者, 小部件和全局过滤器将依赖于他们需要的其他贡献者. 这可以保证多个小部件可以请求javascript库, 并且不会重复.
我们需要将这些贡献者添加到WebModule.cs文件的ConfigureServices方法中的bundling选项:
Configure<BundlingOptions>(options =>
{
options.ScriptBundles.Add(DashboardNames.MyDashboard, configuration =>
{
configuration.AddContributors(typeof(MyDashboardScriptBundleContributor));
});
options.StyleBundles.Add(DashboardNames.MyDashboard, configuration =>
{
configuration.AddContributors(typeof(MyDashboardStyleBundleContributor));
});
});
现在我们可以开始创建小部件了.
小部件
小部件是在将它们添加到仪表板时按顺序呈现的视图组件. 它们也可以在任何你喜欢的地方呈现.
我们将看到如何创建小部件并将其添加到我们创建的仪表板中. 我们将在本教程开头的屏幕截图中创建"每月利润"小部件.
在创建小部件之前,我们需要一个应用程序服务来返回小部件的虚拟数据.
namespace DashboardDemo
{
public interface IDemoStatisticAppService : IApplicationService
{
Task<GetMonthlyUserStatisticDto> GetMonthlyUserStatistic(FilterDto filter);
Task<MonthlyProfitStatisticDto> GetMonthlyProfitStatistic(FilterDto filter);
}
public class DemoStatisticAppService : ApplicationService, IDemoStatisticAppService
{
public async Task<MonthlyProfitStatisticDto> GetMonthlyProfitStatistic(FilterDto filter)
{
var monthCount = GetLabels(filter, out var monthList);
var data = Enumerable
.Repeat(0, monthCount)
.Select(i => new Random().Next(-20, 40))
.ToArray();
return new MonthlyProfitStatisticDto { Labels = monthList.ToArray(), Data = data };
}
private static int GetLabels(FilterDto filter, out List<string> monthList)
{
DateTime endDate = filter.EndDate ?? DateTime.Now;
DateTime startDate = filter.StartDate ?? DateTime.Now.AddYears(-1);
if (filter.StartDate > filter.EndDate)
{
throw new BusinessException("Start date can not be greater than end date.");
}
var months = new[] {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"};
var monthCount = (endDate.Year - startDate.Year) * 12 + endDate.Month - startDate.Month +1;
monthList = new List<string>();
for (int i = 0; i < monthCount; i++)
{
monthList.Add(months[endDate.Month-1]);
endDate = endDate.AddMonths(-1);
}
monthList.Reverse();
return monthCount;
}
}
public class MonthlyProfitStatisticDto
{
public string[] Labels { get; set; }
public int[] Data { get; set; }
}
public class FilterDto
{
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
}
}
我们将在Global Filters中使用FilterDto.
现在我们可以开始处理我们的小部件了.
你可以看到我们将创建小部件的文件. (如果你的小部件不需要css或javascript,则不需要创建它们和贡献者).
首先我们创建 MonthlyProfitWidgetViewComponent:
@inject IHtmlLocalizer<DashboardDemoResource> L
@using DashboardDemo.Localization.DashboardDemo
@using Microsoft.AspNetCore.Mvc.Localization
@model DashboardDemo.Pages.widgets.MonthlyProfitWidgetViewComponent
@{
}
<div id="MonthlyProfitWidgetContainer">
<abp-card background="Light">
<abp-card-header background="Info">@L["Monthly Profit"]</abp-card-header>
<abp-card-body>
<div class="row margin-bottom-5">
<canvas id="MonthlyProfitStatistics"></canvas>
</div>
</abp-card-body>
</abp-card>
</div>
public class MonthlyProfitWidgetViewComponent : AbpViewComponent
{
public const string Name = "MonthlyProfitWidget";
public const string DisplayName = "Monthly Profit Widget";
public IViewComponentResult Invoke()
{
return View("/Pages/widgets/MonthlyProfitWidget/MonthlyProfitWidgetViewComponent.cshtml", new MonthlyProfitWidgetViewComponent());
}
}
我们将使用chart.js library来创建图表. 要将此库添加到项目中,我们将包依赖项添加到package.json:
"dependencies": {
//other dependencies...
"chart.js": "^2.8.0"
}
然后添加映射到abp.resourcemappings.js:(参见相关文档)
mappings: {
//other mappings...
"@node_modules/chart.js/dist/*.*": "@libs/chart.js/"
}
现在我们的应用程序中有chart.js库. 为了使用它,我们将创建它的贡献者:
public class ChartjsScriptContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.Add("/libs/chart.js/Chart.js");
}
}
public class ChartjsStyleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.Add("/libs/chart.js/Chart.css");
}
}
现在我们为小部件文件创建贡献者并使它们依赖于chart.js:
[DependsOn(typeof(JQueryScriptContributor))]
[DependsOn(typeof(ChartjsScriptContributor))]
public class MonthlyProfitWidgetScriptBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.Add("/Pages/widgets/MonthlyProfitWidget/MonthlyProfitWidget.js");
}
}
[DependsOn(typeof(BootstrapStyleContributor))]
[DependsOn(typeof(ChartjsStyleContributor))]
public class MonthlyProfitWidgetStyleBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
context.Files.Add("/Pages/widgets/MonthlyProfitWidget/MonthlyProfitWidget.css");
}
}
MonthlyProfitWidget.css 对于我们的小部件是空的.
MonthlyProfitWidget.js 内容如下:
(function ($) {
var $container = $('#MonthlyProfitWidgetContainer');
if ($container.length > 0) {
var chart = {};
var createChart = function () {
dashboardDemo.demoStatistic.getMonthlyProfitStatistic({}).then(function (result) {
chart = new Chart($container.find('#MonthlyProfitStatistics'), {
type: 'line',
data: {
labels: result.labels,
datasets: [{
label: 'Monthly Profit',
data: result.data,
backgroundColor: 'rgba(255, 255, 132, 0.2)'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
});
};
createChart();
}
})(jQuery);
我们创建了小部件. 在将其添加到仪表板之前还有最后一件事, 我们需要定义它:
public static class WidgetDefinitionProvider
{
public static List<WidgetDefinition> GetDefinitions()
{
//other widgets...
var monthlyProfitWidget = new WidgetDefinition(
MonthlyProfitWidgetViewComponent.Name,
LocalizableString.Create<DashboardDemoResource>(MonthlyProfitWidgetViewComponent.DisplayName),
typeof(MonthlyProfitWidgetViewComponent)
)
.SetDefaultDimension(6, 4)
.AddRequiredPermission(IdentityPermissions.Users.Default);
return new List<WidgetDefinition>
{
//other widgets...
monthlyProfitWidget
};
}
}
SetDefaultDimension(int x, int y): 设置小部件的尺寸. 在仪表板中渲染时将使用此选项. X表示bootstrap中的列宽,可以在1到12之间.Y是以像素为单位的高度,将乘以100.
AddRequiredPermission(string permissionName): 设置窗口小部件的权限. 因此, 没有此权限的用户将看不到此小部件.
我们需要在WebModule.cs文件的ConfigureServices方法中将小部件定义添加到WidgetOptions:
Configure<WidgetOptions>(options =>
{
options.Widgets.AddRange(WidgetDefinitionProvider.GetDefinitions());
});
现在我们的小部件已经可以使用了. 我们将使用DashboardDefinitionProvider.cs中WithWidget方法将其添加到仪表板中:
var myDashboard = new DashboardDefinition(
DashboardNames.MyDashboard,
LocalizableString.Create<DashboardDemoResource>("MyDashboard"))
.WithWidget(MonthlyProfitWidgetViewComponent.Name);
并将javascript和contributor依赖项添加到仪表板:
[DependsOn(typeof(MonthlyProfitWidgetScriptBundleContributor))] // <<<<<<
[DependsOn(typeof(AbpBasicDashboardScriptContributor))]
public class MyDashboardScriptBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
}
}
[DependsOn(typeof(MonthlyProfitWidgetStyleBundleContributor))] // <<<<<<
[DependsOn(typeof(AbpBasicDashboardStyleContributor))]
public class MyDashboardStyleBundleContributor : BundleContributor
{
public override void ConfigureBundle(BundleConfigurationContext context)
{
}
}
现在启动应用程序并转到 /MyDashboard页面.
全局过滤器
全局过滤器用于过滤具有相同输入的所有小部件. 如果向仪表板添加全局过滤器,则会显示刷新按钮,以使用新过滤器值刷新窗口小部件. 单击此按钮时,它会将过滤器序列化为对象,并以该对象作为参数触发事件.
我们来实现一个date range全局过滤器.
首先我们创建 DateRangeGlobalFilterViewComponent.cshtml:
@inject IHtmlLocalizer<DashboardDemoResource> L
@using DashboardDemo.Localization.DashboardDemo
@using Microsoft.AspNetCore.Mvc.Localization
@model DashboardDemo.Pages.widgets.Filters.DateRangeGlobalFilterViewComponent
@{
}
<div id="DateRangeGlobalFilterContainer">
<div class="row">
<div class="col-md-6 mb-3">
<label for="DateFilterStartDateInput">@L["Start Date"].Value</label>
<input class="form-control" type="date" name="StartDate" id="DateFilterStartDateInput">
</div>
<div class="col-md-6 mb-3">
<label for="DateFilterEndDateInput">@L["End Date"].Value</label>
<input class="form-control" type="date" name="EndDate" id="EndDateStartDateInput">
</div>
</div>
</div>
namespace DashboardDemo.Pages.widgets.Filters
{
[ViewComponent]
public class DateRangeGlobalFilterViewComponent : ViewComponent
{
public const string Name = "DateRangeGlobalFilter";
public const string DisplayName = "Date Range Filter";
public IViewComponentResult Invoke()
{
return View("/Pages/widgets/Filters/DateRangeGlobalFilterViewComponent.cshtml", new DateRangeGlobalFilterViewComponent());
}
}
}
您可以像添加到窗口小部件一样添加javascript和css文件,但在此示例中不需要它们.
我们将在WebModule.cs文件的ConfigureServices方法中将全局过滤器定义添加到GlobalFilterOptions:
Configure<GlobalFilterOptions>(options =>
{
options.GlobalFilters.AddRange(GlobalFilterDefinitionProvider.GetDefinitions());
});
并使用WithGlobalFilter方法将其添加到DashboardDefinitionProvider.cs中的仪表板中:
var myDashboard = new DashboardDefinition(
DashboardNames.MyDashboard,
LocalizableString.Create<DashboardDemoResource>("MyDashboard"))
.WithWidget(MonthlyProfitWidgetViewComponent.Name)
.WithGlobalFilter(DateRangeGlobalFilterViewComponent.Name);
现在让我们在小部件中捕获刷新事件:
(function ($) {
var $container = $('#MonthlyProfitWidgetContainer');
if ($container.length > 0) {
var chart = {};
var createChart = function () {
dashboardDemo.demoStatistic.getMonthlyProfitStatistic({}).then(function (result) {
chart = new Chart($container.find('#MonthlyProfitStatistics'), {
type: 'line',
data: {
labels: result.labels,
datasets: [{
label: 'Monthly Profit',
data: result.data,
backgroundColor: 'rgba(255, 255, 132, 0.2)'
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
});
};
$(document).on('RefreshWidgets',
function (event, filters) {
dashboardDemo.demoStatistic.getMonthlyProfitStatistic({ startDate: filters.startDate, endDate: filters.endDate }).then(function (result) {
chart.data = {
labels: result.labels,
datasets: [{
label: 'Monthly Profit',
data: result.data,
backgroundColor: 'rgba(255, 255, 132, 0.2)'
}]
},
chart.update();
});
});
createChart();
}
})(jQuery);
源代码
你可以在Github查看仪表板的示例应用程序

