feat(TableExport): add ExportCsvAsync function (#2179)

* refactor: 增加导出 CSV 方法

* feat: 增加导出 CSV 方法

* chore: bump version 7.10.8

* refactor: 重构代码

* chore: bump version 7.5.1

* refactor: 更新代码

* doc: 本地化

* chore: 更新依赖

* test: 增加本地化

* test: 增加导出 csv 单元测试

* chore: bump version 7.10.8-beta10

* chore: 更新依赖包
This commit is contained in:
Argo Zhang 2023-09-24 20:02:59 +08:00 committed by GitHub
parent 931e604daa
commit 4666b63ef8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 149 additions and 36 deletions

View File

@ -44,7 +44,7 @@
<PackageReference Include="BootstrapBlazor.SignaturePad" Version="7.0.3" />
<PackageReference Include="BootstrapBlazor.Splitting" Version="7.0.0" />
<PackageReference Include="BootstrapBlazor.SummerNote" Version="7.3.4" />
<PackageReference Include="BootstrapBlazor.TableExport" Version="7.5.0" />
<PackageReference Include="BootstrapBlazor.TableExport" Version="7.5.1-beta01" />
<PackageReference Include="BootstrapBlazor.Topology" Version="7.4.5" />
<PackageReference Include="BootstrapBlazor.VideoPlayer" Version="7.0.4" />
<PackageReference Include="BootstrapBlazor.MouseFollower" Version="7.0.0" />

View File

@ -5105,7 +5105,12 @@
"TablesExportOnExportAsyncTitle": "Custom export method",
"TablesExportOnExportAsyncIntro": "You can customize the export method by setting the <code>OnExportAsync</code> callback delegate method. If you don't set it, the built-in export function of the component will be used.",
"TablesExportButtonDropdownTemplateTitle": "Custom export dropdown button",
"TablesExportButtonDropdownTemplateIntro": "Customize the content of the export button dropdown box by setting the <code>ExportButtonDropdownTemplate</code> template"
"TablesExportButtonDropdownTemplateIntro": "Customize the content of the export button dropdown box by setting the <code>ExportButtonDropdownTemplate</code> template",
"TablesExportButtonExcelText": "Export Page data - Excel",
"TablesExportButtonExcelAllText": "Export All data - Excel",
"TablesExportToastTitle": "Export Data",
"TablesExportToastSuccessContent": "Export successauto close after 4 second",
"TablesExportToastFailedContent": "Export failedauto close after 4 second"
},
"BootstrapBlazor.Shared.Samples.Table.TablesToolbar": {
"TablesToolbarTitle": "Table",

View File

@ -5105,7 +5105,12 @@
"TablesExportOnExportAsyncTitle": "自定义导出方法",
"TablesExportOnExportAsyncIntro": "通过设置 <code>OnExportAsync</code> 回调委托方法可自定义导出方法,不设置将使用组件内置导出函数",
"TablesExportButtonDropdownTemplateTitle": "自定义导出下拉框按钮",
"TablesExportButtonDropdownTemplateIntro": "通过设置 <code>ExportButtonDropdownTemplate</code> 模板自定义导出按钮下拉框内容"
"TablesExportButtonDropdownTemplateIntro": "通过设置 <code>ExportButtonDropdownTemplate</code> 模板自定义导出按钮下拉框内容",
"TablesExportButtonExcelText": "导出当前页数据 Excel",
"TablesExportButtonExcelAllText": "导出全部数据 Excel",
"TablesExportToastTitle": "数据导出",
"TablesExportToastSuccessContent": "导出数据成功4 秒后自动关闭",
"TablesExportToastFailedContent": "导出数据失败4 秒后自动关闭"
},
"BootstrapBlazor.Shared.Samples.Table.TablesToolbar": {
"TablesToolbarTitle": "Table 表格",

View File

@ -70,11 +70,15 @@
<TableColumn @bind-Field="@context.Count" />
</TableColumns>
<ExportButtonDropdownTemplate Context="ExportContext">
<div class="dropdown-item" @onclick="ExportContext.ExportAsync">
<div class="dropdown-item" @onclick="() => ExcelExportAsync(ExportContext)">
<i class="fa-regular fa-file-excel"></i>
<span>MS-Excel</span>
<span>@Localizer["TablesExportButtonExcelText"]</span>
</div>
<div class="dropdown-item" @onclick="@CsvExportAsync">
<div class="dropdown-item" @onclick="() => ExcelExportAllAsync(ExportContext)">
<i class="fa-regular fa-file-excel"></i>
<span>@Localizer["TablesExportButtonExcelAllText"]</span>
</div>
<div class="dropdown-item" @onclick="() => CsvExportAsync(ExportContext)">
<i class="fa-regular fa-file-excel"></i>
<span>MS-CSV</span>
</div>

View File

@ -9,6 +9,13 @@ namespace BootstrapBlazor.Shared.Samples.Table;
/// </summary>
public partial class TablesExport
{
/// <summary>
/// ToastService 服务实例
/// </summary>
[Inject]
[NotNull]
private ToastService? Toast { get; set; }
/// <summary>
/// Foo 类为Demo测试用如有需要请自行下载源码查阅
/// Foo class is used for Demo test, please download the source code if necessary
@ -16,12 +23,7 @@ public partial class TablesExport
/// </summary>
[NotNull]
private List<Foo>? Items { get; set; }
private static IEnumerable<int> PageItemsSource => new int[]
{
4,
10,
20
};
private static IEnumerable<int> PageItemsSource => new int[] { 4, 10, 20 };
/// <summary>
/// <inheritdoc/>
@ -43,23 +45,74 @@ public partial class TablesExport
return Task.FromResult(new QueryData<Foo>() { Items = items, TotalCount = total });
}
private Task CsvExportAsync()
private async Task<bool> OnExportAsync(ITableExportDataContext<Foo> context)
{
//your code ...
// 自定义导出方法
// 通过 context 参数可以自己查询数据进行导出操作
// 本例使用 context 传递来的 Rows/Columns 自定义文件名为 Test.xlsx
var ret = await Exporter.ExportAsync(context.Rows, context.Columns, "Test.xlsx");
return ToastService.Success("CSV export", "Export CSV data successfully");
// 返回 true 时自动弹出提示框
return ret;
}
[Inject]
[NotNull]
private ITableExcelExport? Exporter { get; set; }
private async Task<bool> OnExportAsync(ITableExportDataContext<Foo> context)
private async Task ExcelExportAsync(ITableExportContext<Foo> context)
{
// your code ...
var ret = await Exporter.ExportAsync(context.Rows, context.Columns, "Test.xlsx");
// 自定义导出模板导出当前页面数据为 Excel 方法
// 使用 BootstrapBlazor 内置服务 ITableExcelExport 实例方法 ExportAsync 进行导出操作
// 导出数据使用 context 传递来的 Rows/Columns 即为当前页数据
var ret = await Exporter.ExportAsync(context.Rows, context.Columns, $"Test_{DateTime.Now:yyyyMMddHHmmss}.xlsx");
// 返回 true 时自动弹出提示框
return ret;
await ShowToast(ret);
}
private async Task ExcelExportAllAsync(ITableExportContext<Foo> context)
{
// 自定义导出模板导出当前页面数据为 Excel 方法
// 使用 BootstrapBlazor 内置服务 ITableExcelExport 实例方法 ExportAsync 进行导出操作
// 通过 context 参数的查询条件
var option = context.BuildQueryPageOptions();
// 通过内置扩展方法 ToFilter 获得所有条件
var filter = option.ToFilter();
// 通过内置扩展方法 GetFilterFunc 过滤数据
// EFCore 可使用 GetFilterLambda 获得表达式直接给 Where 方法使用
var data = Items.Where(filter.GetFilterFunc<Foo>());
// 导出符合条件的所有数据 data
var ret = await Exporter.ExportAsync(data, context.Columns, $"Test_{DateTime.Now:yyyyMMddHHmmss}.xlsx");
// 返回 true 时自动弹出提示框
await ShowToast(ret);
}
private async Task CsvExportAsync(ITableExportContext<Foo> context)
{
// 自定义导出模板导出当前页面数据为 Csv 方法
// 使用 BootstrapBlazor 内置服务 ITableExcelExport 实例方法 ExportCsvAsync 进行导出操作
// 导出数据使用 context 传递来的 Rows/Columns 即为当前页数据
var ret = await Exporter.ExportCsvAsync(context.Rows, context.Columns, $"Test_{DateTime.Now:yyyyMMddHHmmss}.csv");
// 返回 true 时自动弹出提示框
await ShowToast(ret);
}
private async Task ShowToast(bool result)
{
if (result)
{
await Toast.Success(Localizer["TablesExportToastTitle"], Localizer["TablesExportToastSuccessContent"]);
}
else
{
await Toast.Error(Localizer["TablesExportToastTitle"], Localizer["TablesExportToastFailedContent"]);
}
}
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<Version>7.10.8-beta09</Version>
<Version>7.10.8-beta10</Version>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">

View File

@ -1016,9 +1016,13 @@ public partial class Table<TItem>
}
}
private Task ExportAsync() => ExecuteExportAsync(() => OnExportAsync != null ? OnExportAsync(new TableExportDataContext<TItem>(TableExportType.Unknown, Rows, GetVisibleColumns(), BuildQueryPageOptions())) : Task.FromResult(false));
private Task ExportAsync() => ExecuteExportAsync(() => OnExportAsync != null
? OnExportAsync(new TableExportDataContext<TItem>(TableExportType.Unknown, Rows, GetVisibleColumns(), BuildQueryPageOptions()))
: ExcelExport.ExportAsync(Rows, GetVisibleColumns()));
private Task ExportPdfAsync() => ExecuteExportAsync(() => OnExportAsync != null ? OnExportAsync(new TableExportDataContext<TItem>(TableExportType.Pdf, Rows, GetVisibleColumns(), BuildQueryPageOptions())) : PdfExport.ExportAsync(Rows, GetVisibleColumns()));
private Task ExportPdfAsync() => ExecuteExportAsync(() => OnExportAsync != null
? OnExportAsync(new TableExportDataContext<TItem>(TableExportType.Pdf, Rows, GetVisibleColumns(), BuildQueryPageOptions()))
: PdfExport.ExportAsync(Rows, GetVisibleColumns()));
private Task ExportExcelAsync() => ExecuteExportAsync(() => OnExportAsync != null
? OnExportAsync(new TableExportDataContext<TItem>(TableExportType.Excel, Rows, GetVisibleColumns(), BuildQueryPageOptions()))

View File

@ -10,7 +10,20 @@ class DefaultExcelExport : ITableExcelExport
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
public Task<bool> ExportAsync<TItem>(IEnumerable<TItem> items, IEnumerable<ITableColumn>? cols = null, string? fileName = null) where TItem : class
public Task<bool> ExportAsync<TItem>(IEnumerable<TItem> items, IEnumerable<ITableColumn>? cols = null, string? fileName = null)
{
return Task.FromResult(false);
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <typeparam name="TItem"></typeparam>
/// <param name="items"></param>
/// <param name="cols"></param>
/// <param name="fileName"></param>
/// <returns></returns>
public Task<bool> ExportCsvAsync<TItem>(IEnumerable<TItem> items, IEnumerable<ITableColumn>? cols, string? fileName = null)
{
return Task.FromResult(false);
}

View File

@ -15,5 +15,13 @@ public interface ITableExcelExport
/// <param name="items">导出数据集合</param>
/// <param name="cols">当前可见列数据集合 默认 null 导出全部列</param>
/// <param name="fileName">文件名 默认 null ExportData_{DateTime.Now:yyyyMMddHHmmss}.xlsx</param>
Task<bool> ExportAsync<TItem>(IEnumerable<TItem> items, IEnumerable<ITableColumn>? cols, string? fileName = null) where TItem : class;
Task<bool> ExportAsync<TModel>(IEnumerable<TModel> items, IEnumerable<ITableColumn>? cols, string? fileName = null);
/// <summary>
/// 导出 Csv 方法
/// </summary>
/// <param name="items">导出数据集合</param>
/// <param name="cols">当前可见列数据集合 默认 null 导出全部列</param>
/// <param name="fileName">文件名 默认 null ExportData_{DateTime.Now:yyyyMMddHHmmss}.xlsx</param>
Task<bool> ExportCsvAsync<TModel>(IEnumerable<TModel> items, IEnumerable<ITableColumn>? cols, string? fileName = null);
}

File diff suppressed because one or more lines are too long

View File

@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<Version>7.5.0</Version>
<Version>7.5.1-beta01</Version>
<PackageTags>Bootstrap Blazor WebAssembly wasm UI Components Table Export</PackageTags>
<Description>Bootstrap UI components extensions of export</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BootstrapBlazor" Version="7.6.6" />
<PackageReference Include="BootstrapBlazor" Version="7.10.8-beta10" />
<PackageReference Include="MiniExcel" Version="1.30.3" />
</ItemGroup>

View File

@ -27,24 +27,31 @@ class ExcelExport : ITableExcelExport
/// <param name="cols">导出列集合 默认 null 全部导出</param>
/// <param name="fileName">导出后下载文件名</param>
/// <returns></returns>
public async Task<bool> ExportAsync<TModel>(IEnumerable<TModel> items, IEnumerable<ITableColumn>? cols = null, string? fileName = null) where TModel : class
public Task<bool> ExportAsync<TModel>(IEnumerable<TModel> items, IEnumerable<ITableColumn>? cols = null, string? fileName = null) => InternalExportAsync(items, cols, ExcelType.XLSX, fileName);
public Task<bool> ExportCsvAsync<TModel>(IEnumerable<TModel> items, IEnumerable<ITableColumn>? cols, string? fileName = null) => InternalExportAsync(items, cols, ExcelType.CSV, fileName);
private async Task<bool> InternalExportAsync<TModel>(IEnumerable<TModel> items, IEnumerable<ITableColumn>? cols, ExcelType excelType, string? fileName = null)
{
var value = new List<Dictionary<string, object?>>();
cols ??= Utility.GetTableColumns<TModel>();
foreach (var item in items)
{
var row = new Dictionary<string, object?>();
foreach (var pi in cols)
if (item != null)
{
var val = await FormatValue(pi, Utility.GetPropertyValue(item, pi.GetFieldName()));
row.Add(pi.GetDisplayName(), val);
var row = new Dictionary<string, object?>();
foreach (var pi in cols)
{
var val = await FormatValue(pi, Utility.GetPropertyValue(item, pi.GetFieldName()));
row.Add(pi.GetDisplayName(), val);
}
value.Add(row);
}
value.Add(row);
}
using var stream = new MemoryStream();
await MiniExcel.SaveAsAsync(stream, value);
await MiniExcel.SaveAsAsync(stream, value, excelType: excelType);
fileName ??= $"ExportData_{DateTime.Now:yyyyMMddHHmmss}.xlsx";
fileName ??= $"ExportData_{DateTime.Now:yyyyMMddHHmmss}.csv";
stream.Position = 0;
var downloadService = ServiceProvider.GetRequiredService<DownloadService>();
await downloadService.DownloadFromStreamAsync(fileName, stream);

View File

@ -844,6 +844,7 @@ public class TableTest : TableTestBase
ITableExportDataContext<Foo>? exportContext = null;
bool exported = false;
var localizer = Context.Services.GetRequiredService<IStringLocalizer<Foo>>();
var export = Context.Services.GetRequiredService<ITableExcelExport>();
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
{
pb.AddChildContent<Table<Foo>>(pb =>
@ -855,9 +856,18 @@ public class TableTest : TableTestBase
{
context = c;
builder.OpenElement(0, "div");
builder.AddAttribute(1, "onclick", EventCallback.Factory.Create<MouseEventArgs>(this, c.ExportAsync));
builder.AddAttribute(1, "onclick", EventCallback.Factory.Create<MouseEventArgs>(this, context.ExportAsync));
builder.AddContent(2, "test-export-dropdown-item");
builder.CloseElement();
// csv 按钮
builder.OpenElement(10, "div");
builder.AddAttribute(11, "onclick", EventCallback.Factory.Create<MouseEventArgs>(this, async () =>
{
await export.ExportCsvAsync(context.Rows, context.Columns);
}));
builder.AddAttribute(12, "class", "test-export-dropdown-csv-item");
builder.CloseElement();
});
pb.Add(a => a.Items, Foo.GenerateFoo(localizer));
pb.Add(a => a.TableColumns, foo => builder =>
@ -884,6 +894,10 @@ public class TableTest : TableTestBase
Assert.Equal(80, context.Rows.Count());
Assert.NotNull(context.ExportAsync());
// 导出 csv
var csv = cut.Find(".test-export-dropdown-csv-item");
cut.InvokeAsync(() => csv.Click());
var table = cut.FindComponent<Table<Foo>>();
table.SetParametersAndRender(pb =>
{