Bootstrap Blazor + FreeSql 实战 Chart 图表使用(2)
接上篇 BootstrapBlazor实战 Chart 图表使用(1)
13.添加必备的库
使用 nuget.org 进行 BootstrapBlazor 组件安装, FreeSql库,Newtonsoft.Json
dotnet add b06chart package Densen.FreeSql.Extensions.BootstrapBlazor
dotnet add b06chart package FreeSql.Provider.Sqlite
dotnet add b06chart package Newtonsoft.Json
14. 数据服务
添加FreeSql服务到 Program.cs
到
在 builder.Services.AddBootstrapBlazor();
之前加入
builder.Services.AddFreeSql(option =>
{
//demo演示的是Sqlite驱动,FreeSql支持多种数据库,MySql/SqlServer/PostgreSQL/Oracle/Sqlite/Firebird/达梦/神通/人大金仓/翰高/华为GaussDB/MsAccess
option.UseConnectionString(FreeSql.DataType.Sqlite, "Data Source=test.db;") //也可以写到配置文件中
#if DEBUG
//开发环境:自动同步实体
.UseAutoSyncStructure(true)
.UseNoneCommandParameter(true)
//调试sql语句输出
.UseMonitorCommand(cmd => System.Console.WriteLine(cmd.CommandText))
#endif
;
});
15. 添加实体类Model/OrdersEntry.cs
using BootstrapBlazor.Components;
using FreeSql.DataAnnotations;
using Newtonsoft.Json;
using System.ComponentModel;
using System.Linq;
namespace Blazor100.Data;
public partial class Orders
{
/// <summary>
/// 流水号
/// </summary>
[AutoGenerateColumn(Editable = false, DefaultSort = true, DefaultSortOrder = SortOrder.Desc, Order = 1)]
[JsonProperty, Column(IsIdentity = true)]
[DisplayName("流水号")]
public int OrderID { get; set; }
/// <summary>
/// 单据日期
/// </summary>
[AutoGenerateColumn(FormatString = "yyyy-MM-dd", ComponentType = typeof(DatePickerBody))]
[JsonProperty]
[DisplayName("日期")]
public DateTime OrderDate { get; set; }
/// <summary>
/// 合计金额
/// </summary>
[AutoGenerateColumn(FormatString = "N2", Align = Alignment.Right)]
[JsonProperty, Column(DbType = "decimal(19,4)")]
[DisplayName("合计")]
public decimal SubTotal { get; set; }
[AutoGenerateColumn(Ignore = true)]
[Navigate(nameof(OrderID))]
public virtual List<OrderDetails>? OrderDetailss { get; set; }
}
}
/// <summary>
/// 订单详单
/// </summary>
public partial class OrderDetails
{
[JsonProperty, Column(IsIdentity = true)]
public int ID { get; set; }
[JsonProperty]
public int OrderID { get; set; }
[JsonProperty, Column(StringLength = -1)]
[DisplayName("条码")]
public string? BarCode { get; set; }
[AutoGenerateColumn(FormatString = "N0", Align = Alignment.Center)]
[JsonProperty, Column(DbType = "numeric(18,3)")]
[DisplayName("数量")]
public decimal Quantity { get; set; }
[AutoGenerateColumn(Ignore = true)]
[Navigate(nameof(OrderID))]
public virtual Orders Orders { get; set; }
}
16. 添加命名空间引用Shared/_Imports.razor
@using Blazor100.Data
17.添加NavLink到Shared/NavMenu.razor
<div class="nav-item px-3">
<NavLink class="nav-link" href="DayReport">
月报
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="TopSales">
排行榜
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="YearsCharts">
年报
</NavLink>
</div>
18.将组件Chart封装,添加文件Components/ChartsBase.razor
@namespace b06chart
@if (!IsHideSelectores)
{
<span> @Year 年</span>
<span> @Month 月</span>
<span>
合计 : @(Total.ToString("N2"))
@TotalString2
@TotalString3
</span>
}
<div class="text-center mt-2 chart">
@if (!IsHideSelectores && UseDateTimeRangeValue)
{
<DateTimeRange @bind-Value="@DateTimeRangeValue1" OnConfirm="OnConfirm" OnClearValue="OnClear" />
}
<div class="btn-group">
@if (!IsHideSelectores)
{
for (int i = DateTime.Now.Year - 7; i <= DateTime.Now.Year; i++)
{
var year = i;
<Button Color="Color.Primary" IsOutline="@(Year!=year)" Text="@year.ToString()"
OnClick="(()=>SetYear(year))" />
}
}
<Button Color="Color.Primary" IsOutline="true" OnClick="SwitchChart"><i class="fa @(IsLineChart?"fa-bar-chart":"fa-line-chart")"></i><span>切换</span></Button>
<Button Color="Color.Primary" IsOutline="true" OnClick="SwitchStacked"><i class="fa @(IsStacked?"fa-toggle-on":"fa-toggle-off")"></i><span>@(IsStacked? "合并" : "不合并")</span></Button>
<Button Color="Color.Primary" IsOutline="true" OnClick="e=>ReloadChart(true)"><i class="fa fa-refresh"></i><span>刷新</span></Button>
</div>
</div>
@if (!IsHideSelectores && IsShowMonthSelector)
{
<div class="text-center mt-2 chart">
<div class="btn-group">
@{
for (int i = 1; i <= 12; i++)
{
var month = i;
<Button Color="Color.Primary" IsOutline="@(Month!=month)" Text="@month.ToString()"
OnClick="(()=>SetMonth(month))" />
}
}
<Button Color="Color.Primary" IsOutline="true" OnClick="PreMonth"><i class="fa fa-calendar-minus-o"></i><span>上月</span></Button>
<Button Color="Color.Primary" IsOutline="true" OnClick="NextMonth"><i class="fa fa-calendar-plus-o"></i><span>下月</span></Button>
<Button Color="Color.Primary" IsOutline="true" OnClick="SetNow"><i class="fa fa-calendar-check-o"></i><span>本月</span></Button>
</div>
</div>
}
<div style="width: calc(80%);display: block;margin: 0 auto;">
@if (Show)
{
if (!IsLineChart)
{
<Chart ChartType="ChartType.Bar" OnInitAsync="OnInit" @ref="BarChart" Width="" />
}
else
{
<Chart OnInitAsync="OnInit" @ref="LineChart" />
}
}
</div>
添加后置代码 Components/ChartsBase.razor.cs
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;
using System.Diagnostics.CodeAnalysis;
namespace b06chart
{
public partial class ChartsBase
{
private Chart? LineChart { get; set; }
private Chart? BarChart { get; set; }
/// <summary>
/// 设定当前年份
/// </summary>
[Parameter] public int Year { get; set; } = DateTime.Now.Year;
/// <summary>
/// 设定当前月份
/// </summary>
[Parameter] public int Month { get; set; } = DateTime.Now.Month;
/// <summary>
/// 设定图表抬头
/// </summary>
[Parameter] public string TitleCharts { get; set; } = "日报表";
/// <summary>
/// 设定X轴文本
/// </summary>
[Parameter] public string XAxesText { get; set; } = "天数";
/// <summary>
/// 设定Y轴文本
/// </summary>
[Parameter] public string YAxesText { get; set; } = "数值";
/// <summary>
/// 图表类型:是=LineChart,否=BarChart
/// </summary>
[Parameter] public bool IsLineChart { get; set; }
/// <summary>
/// 使用默认数据
/// </summary>
[Parameter] public bool IsDemo { get; set; }
/// <summary>
/// 显示月份选择器
/// </summary>
[Parameter] public bool IsShowMonthSelector { get; set; } = true;
[Parameter] public EventCallback<ChartDataSource> OnInitCallback { get; set; }
[Parameter] public EventCallback<ChartDataSource> 数据生成Callback { get; set; }
[Parameter] public decimal Total { get; set; }
[Parameter] public string? TotalString2 { get; set; }
[Parameter] public string? TotalString3 { get; set; }
/// <summary>
/// 隐藏选择器
/// </summary>
[Parameter] public bool IsHideSelectores { get; set; }
/// <summary>
/// 使用/初始化日期选择控件日期
/// </summary>
[Parameter] public bool UseDateTimeRangeValue { get; set; }
/// <summary>
/// 是否合并Bar显示 默认false
/// </summary>
public bool IsStacked { get; set; }
/// <summary>
/// 强刷显示控件控制,Hack一下
/// </summary>
private bool Show { get; set; } = true;
public int LastCount { get; set; }
public bool FirstLoad { get; set; } = true;
public bool ForceRefresh { get; set; }
private string? ClickItemID { get; set; }
private IEnumerable<string> Colors { get; set; } = new List<string>() { "Blue", "Green", "Red", "Orange", "Yellow", "Tomato", "Pink", "Violet" };
#region 日期选择控件
private DateTimeRangeValue DateTimeRangeValue1 { get; set; } = new DateTimeRangeValue();
DateTime 起始日期 = DateTime.Today.FirstDay();
DateTime 结束日期 = DateTime.Today.LastDay();
private Task OnConfirm(DateTimeRangeValue value)
{
起始日期 = value.Start.FirstSecond();
结束日期 = value.End.Year == 1 ? value.Start.LastSecond() : value.End.LastSecond();
Chart? chart = IsLineChart ? LineChart : BarChart;
chart?.Update(ChartAction.Update);
//StateHasChanged();
return Task.CompletedTask;
}
private Task OnClear(DateTimeRangeValue value)
{
起始日期 = DateTime.Today.FirstDay();
结束日期 = DateTime.Today.LastDay();
Chart? chart = IsLineChart ? LineChart : BarChart;
chart?.Update(ChartAction.Update);
//StateHasChanged();
return Task.CompletedTask;
}
/// <summary>
/// 设置日期选择控件日期
/// </summary>
/// <param name="_起始日期"></param>
/// <param name="_结束日期"></param>
/// <returns></returns>
protected Task SetDates(DateTime _起始日期, DateTime _结束日期)
{
起始日期 = _起始日期;
结束日期 = _结束日期;
DateTimeRangeValue1.Start = 起始日期;
DateTimeRangeValue1.End = 结束日期;
return Task.CompletedTask;
}
#endregion
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (UseDateTimeRangeValue && firstRender) {
DateTimeRangeValue1.Start = 起始日期;
DateTimeRangeValue1.End = 结束日期;
}
}
private Task OnAfterInit()
{
System.Console.WriteLine("Bar 初始化完毕");
return Task.CompletedTask;
}
/// <summary>
/// 初始化 ChartDataSource
/// </summary>
/// <returns></returns>
protected Task<ChartDataSource> OnInit()
{
var ds = new ChartDataSource();
if (!OnInitCallback.HasDelegate)
{
ds.Options.Title = TitleCharts;
ds.Options.X.Title = XAxesText;
ds.Options.X.Stacked = IsStacked;
ds.Options.Y.Title = YAxesText;
ds.Options.Y.Stacked = IsStacked;
}
else
{
OnInitCallback.InvokeAsync(ds);
}
//设置自定义颜色
ds.Options.Colors = new Dictionary<string, string>() {
{ "blue:", "rgb(54, 162, 235)" },
{ "green:", "rgb(75, 192, 192)" },
{ "red:", "rgb(255, 99, 132)" },
{ "orange:", "rgb(255, 159, 64)" },
{ "yellow:", "rgb(255, 205, 86)" },
{ "tomato:", "rgb(255, 99, 71)" },
{ "pink:", "rgb(255, 192, 203)" },
{ "violet:", "rgb(238, 130, 238)" },
};
if (!数据生成Callback.HasDelegate)
数据生成(ds);
else
数据生成Callback.InvokeAsync(ds);
if (ds.Labels ==null || ds.Labels!.Count() == 0)
{
LastCount = 0;
Show = false;
return Task.FromResult(ds);
}
Show = true;
ForceRefresh = LastCount == 0 || LastCount < ds.Labels!.Count();
LastCount = ds.Labels!.Count();
if (!FirstLoad && ForceRefresh)
{
ReloadChart();
ForceRefresh = false;
}
FirstLoad = false;
return Task.FromResult(ds);
}
/// <summary>
/// 数据生成,添加Labels和ChartDataset
/// </summary>
/// <param name="ds"></param>
protected virtual void 数据生成(ChartDataSource ds)
{
}
private Task SetYear(int year)
{
Chart? chart = IsLineChart ? LineChart : BarChart;
Year = year;
chart?.Update(ChartAction.Update);
return Task.CompletedTask;
}
private Task SetMonth(int month)
{
Chart? chart = IsLineChart ? LineChart : BarChart;
Month = month;
chart?.Update(ChartAction.Update);
return Task.CompletedTask;
}
private Task PreMonth()
{
Chart? chart = IsLineChart ? LineChart : BarChart;
Year = Month - 1 >= 1 ? Year : Year - 1;
Month = Month - 1 >= 1 ? Month - 1 : 12;
chart?.Update(ChartAction.Update);
return Task.CompletedTask;
}
private Task NextMonth()
{
Chart? chart = IsLineChart ? LineChart : BarChart;
Year = Month + 1 <= 12 ? Year : Year + 1;
Month = Month + 1 <= 12 ? Month + 1 : 1;
chart?.Update(ChartAction.Update);
return Task.CompletedTask;
}
private Task SetNow()
{
Chart? chart = IsLineChart ? LineChart : BarChart;
Year = DateTime.Now.Year;
Month = DateTime.Now.Month;
chart?.Update(ChartAction.Update);
return Task.CompletedTask;
}
private Task RandomData()
{
Chart? chart = IsLineChart ? LineChart : BarChart;
chart?.Update(ChartAction.Update);
return Task.CompletedTask;
}
private Task SwitchChart()
{
IsLineChart = !IsLineChart;
return Task.CompletedTask;
}
/// <summary>
/// 切换合并显示
/// </summary>
private void SwitchStacked()
{
IsStacked = !IsStacked;
ReloadChart();
}
/// <summary>
/// 强刷控件,重新初始化控件外观
/// </summary>
private async void ReloadChart(bool reloadData=false)
{
Chart? chart = IsLineChart ? LineChart : BarChart;
if (reloadData) chart?.Update(ChartAction.Update);
Show = false;
await InvokeAsync(StateHasChanged);
await Task.Delay(1);
Show = true;
await InvokeAsync(StateHasChanged);
}
}
public static class DateTimeExtensions
{
public static DateTime FirstDay(this DateTime obj) => new DateTime(obj.Year, obj.Month, 1, 0, 0, 0);
public static DateTime LastDay(this DateTime obj) => obj.FirstDay().AddMonths(1).AddDays(-1).LastSecond();
public static DateTime FirstSecond(this DateTime obj) => new DateTime(obj.Year, obj.Month, obj.Day, 0, 0, 0);
public static DateTime LastSecond(this DateTime obj) => new DateTime(obj.Year, obj.Month, obj.Day, 23, 59, 59);
}
}
19.添加日报表页面`Pages/DayReport.razor'
@page "/DayReport"
@namespace b06chart
<Tab>
<TabItem Text="日报表">
<ChartsBase @ref="charts"
TitleCharts="日报表"
数据生成Callback="@((ds)=>数据生成(ds))"
Total="@Total" TotalString2="@TotalString2" />
</TabItem>
<TabItem Text="数据">
<Table TItem="Orders"
IsPagination="true"
IsStriped="true"
IsBordered="true"
AutoGenerateColumns="true"
ShowSearch="true"
ShowToolbar="true"
ShowExtendButtons="true"
DoubleClickToEdit=true
ShowColumnList=true
ShowCardView=true>
</Table>
</TabItem>
</Tab>
添加后置代码 Pages/DayReport.razor.cs
using Blazor100.Data;
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;
using System.Diagnostics.CodeAnalysis;
namespace b06chart
{
public partial class DayReport
{
[Inject]
[NotNull]
IFreeSql? fsql { get; set; }
[Inject] ToastService? toastService { get; set; }
List<Orders> orders { get; set; } = new List<Orders>();
ChartsBase? charts;
decimal Total { get; set; }
string? TotalString2 { get; set; }
private Task 数据生成(ChartDataSource ds)
{
var orders = fsql.Select<Orders>()
.Where(a => a.OrderDate.Month == charts!. Month &&
a.OrderDate.Year == charts.Year)
.GroupBy(a => new
{
a.OrderDate.Day
})
.ToList(a => new
{
cou1 = a.Count(),
OrderDate = a.Key.Day,
Total = a.Sum(a.Value.SubTotal)
});
orders = orders.OrderBy(a => a.OrderDate).ToList();
ds.Labels = orders.Select(a => a.OrderDate.ToString());
ds.Data.Add(new ChartDataset()
{
Label = $"单据数",
Data = orders.Select(a => a.cou1).Cast<object>()
});
ds.Data.Add(new ChartDataset()
{
Label = $"金额",
Data = orders.Select(a => a.Total).Cast<object>()
});
Total = orders.Select(a => a.Total).Sum();
return Task.CompletedTask;
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Orders.DemoDatas(fsql!);
}
}
}
}
20.添加年报表页面`Pages/YearsCharts.razor'
@page "/YearsCharts"
@namespace b06chart
<ChartsBase @ref="charts" TitleCharts="年报表" XAxesText="月"
IsShowMonthSelector="false"
数据生成Callback="@((ds)=>数据生成(ds))"
Total="@Total" />
添加后置代码 Pages/YearsCharts.razor.cs
using Blazor100.Data;
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;
using System.Diagnostics.CodeAnalysis;
namespace b06chart
{
public partial class YearsCharts
{
[Inject]
[NotNull]
IFreeSql? fsql { get; set; }
[Inject] ToastService? toastService { get; set; }
List<Orders> orders { get; set; } = new List<Orders>();
ChartsBase? charts;
decimal Total { get; set; }
string? TotalString2 { get; set; }
private Task 数据生成(ChartDataSource ds)
{
var orders = fsql.Select<Orders>()
.Where(a => a.OrderDate.Year == charts!.Year)
.GroupBy(a => new
{
a.OrderDate.Month
})
.ToList(a => new
{
cou1 = a.Count(),
OrderDate = a.Key.Month,
Total = a.Sum(a.Value.SubTotal)
});
orders = orders.OrderBy(a => a.OrderDate).ToList();
ds.Labels = orders.Select(a => a.OrderDate.ToString());
ds.Data.Add(new ChartDataset()
{
Label = $"单据数",
Data = orders.Select(a => a.cou1).Cast<object>()
});
ds.Data.Add(new ChartDataset()
{
Label = $"金额",
Data = orders.Select(a => a.Total).Cast<object>()
});
Total = orders.Select(a => a.Total).Sum();
return Task.CompletedTask;
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Orders.DemoDatas(fsql!);
}
}
}
}
21.添加排行榜页面Pages/OrdersTopSalesCharts.razor
@page "/TopSales"
@namespace b06chart
<Tab>
<TabItem Text="销售排行榜">
<ChartsBase @ref="charts"
TitleCharts="销售排行榜"
数据生成Callback="@((ds)=>数据生成(ds))"
Total="@Total" />
</TabItem>
<TabItem Text="数据">
<Table TItem="OrderDetails"
IsPagination="true"
IsStriped="true"
IsBordered="true"
AutoGenerateColumns="true"
ShowSearch="true"
ShowToolbar="true"
ShowExtendButtons="true"
DoubleClickToEdit=true
ShowColumnList=true
ShowCardView=true>
</Table>
</TabItem>
</Tab>
添加后置代码 Pages/OrdersTopSalesCharts.razor.cs
using Blazor100.Data;
using BootstrapBlazor.Components;
using Microsoft.AspNetCore.Components;
namespace b06chart
{
public partial class OrdersTopSalesCharts
{
[Inject] IFreeSql? fsql { get; set; }
[Inject] ToastService? toastService { get; set; }
List<OrderDetails> orders { get; set; } = new List<OrderDetails>();
ChartsBase? charts;
decimal Total { get; set; }
private Task 数据生成(ChartDataSource ds)
{
var 起始日期 = (new DateTime(charts!.Year, charts.Month, 1)).FirstDay();
var 结束日期 = 起始日期.LastDay();
orders = fsql!.Select<OrderDetails>()
.Where( a => a.Orders.OrderDate.Between(起始日期, 结束日期))
.GroupBy(a => a.BarCode )
.OrderByDescending(a => a.Sum(a.Value.Quantity))
.ToList(a => new OrderDetails
{
BarCode = a.Key,
Quantity = a.Sum(a.Value.Quantity)
}
);
ds.Labels = orders.Select(a => $"{a.BarCode}");
ds.Data.Add(new ChartDataset()
{
Label = $"销售量",
Data = orders.Select(a => a.Quantity).Cast<object>()
});
Total = orders.Sum(a => a.Quantity);
return Task.CompletedTask;
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Orders.DemoDatas(fsql!);
}
}
}
}
22.本次封装代码来源于本人实际项目,逻辑部分还存在很大优化的空间,目前还有个别bug没整理干净. 望大家有更好的方案,在评论区多多指正,我一定虚心学习, 共同进步.
项目源码
关联项目
FreeSql QQ群:4336577(已满)、8578575(已满)、52508226(在线)
BA & Blazor QQ群:795206915、675147445
知识共享许可协议
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名AlexChow(包含链接: https://github.com/densen2014 ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系 。
AlexChow
关联项目
FreeSql QQ群:4336577
BA & Blazor QQ群:795206915
Maui Blazor 中文社区 QQ群:645660665
知识共享许可协议
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名AlexChow(包含链接: https://github.com/densen2014 ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系 。
转载声明
本文来自博客园,作者:周创琳 AlexChow,转载请注明原文链接:https://www.cnblogs.com/densen2014/p/16098332.html
AlexChow
今日头条 | 博客园 | 知乎 | Gitee | GitHub