mirror of
https://gitee.com/LongbowEnterprise/BootstrapBlazor.git
synced 2024-11-30 02:58:37 +08:00
!2791 test(#I581BH): add unit test for table toolbar
* test: 增加 扩展行内按钮 Callback 单元测试 * test: 增加数据导出单元测试 * feat: 增加导出数据相关资源文件 * test: 增加 确认删除 单元测试 * test: 增加删除确认按钮单元测试 * refactor: 移除 Items 双向绑定 * chore: 删除不使用的图片 * style: 微调 logo 大小 * doc: 更新 band logo * style: 微调后台模板样式 * style: 微调分页组件高度
This commit is contained in:
parent
ed51435e4d
commit
28407c9128
@ -61,7 +61,7 @@
|
||||
</Header>
|
||||
<Side>
|
||||
<div class="layout-banner">
|
||||
<img class="layout-logo" src="_content/BootstrapBlazor.Shared/images/brand.png" />
|
||||
<img class="layout-logo" src="_content/BootstrapBlazor.Shared/images/logo.png" />
|
||||
<div class="layout-title">
|
||||
<span>后台管理</span>
|
||||
</div>
|
||||
|
@ -14,7 +14,7 @@
|
||||
</Header>
|
||||
<Side>
|
||||
<div class="layout-banner">
|
||||
<img class="layout-logo" src="_content/BootstrapBlazor.Shared/images/brand.png" />
|
||||
<img class="layout-logo" src="_content/BootstrapBlazor.Shared/images/logo.png" />
|
||||
<div class="layout-title">
|
||||
<span>后台管理</span>
|
||||
</div>
|
||||
|
@ -5,6 +5,7 @@
|
||||
--bb-violet-rgb: 112.520718,44.062154,249.437846;
|
||||
--bb-bg-violet: #7a5cff;
|
||||
--bb-bg-navbar: #8548ff;
|
||||
--bb-layout-sidebar-banner-bg: #702cf8;
|
||||
}
|
||||
|
||||
header {
|
||||
@ -460,6 +461,10 @@ section {
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.layout-logo {
|
||||
border: solid 1px #fff;
|
||||
}
|
||||
|
||||
.layout-footer .footer {
|
||||
background: #7dbcea;
|
||||
color: #fff;
|
||||
@ -928,7 +933,7 @@ section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 .625rem;
|
||||
background-color: #367fa9;
|
||||
background-color: var(--bb-layout-sidebar-banner-bg);
|
||||
height: 50px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
@ -1309,7 +1314,7 @@ section {
|
||||
|
||||
.layout-item .layout-left .layout-left-header {
|
||||
height: 16px;
|
||||
background-color: #367fa9;
|
||||
background-color: var(--bb-layout-sidebar-banner-bg);
|
||||
}
|
||||
|
||||
.layout-item .layout-left .layout-left-body,
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 23 KiB |
@ -4,6 +4,7 @@
|
||||
--bb-layout-footer-height: 90px;
|
||||
--bb-layout-side-width: 214px;
|
||||
--bb-layout-sidebar-collapse-width: 70px;
|
||||
--bb-layout-sidebar-banner-bg: #367fa9;
|
||||
}
|
||||
|
||||
.layout {
|
||||
@ -123,7 +124,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 0.625rem;
|
||||
background-color: #367fa9;
|
||||
background-color: var(--bb-layout-sidebar-banner-bg);
|
||||
height: var(--bb-layout-header-height);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
@ -135,7 +136,7 @@
|
||||
}
|
||||
|
||||
.layout.is-page .layout-side .layout-banner .layout-logo {
|
||||
width: 44px;
|
||||
width: 42px;
|
||||
border-radius: var(--bs-border-radius);
|
||||
}
|
||||
|
||||
|
@ -47,6 +47,7 @@
|
||||
|
||||
.page-link .fa {
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
@ -326,5 +326,8 @@ public partial class Table<TItem>
|
||||
SortDescText ??= Localizer[nameof(SortDescText)];
|
||||
EmptyText ??= Localizer[nameof(EmptyText)];
|
||||
LineNoText ??= Localizer[nameof(LineNoText)];
|
||||
ExportToastTitle ??= Localizer[nameof(ExportToastTitle)];
|
||||
ExportToastContent ??= Localizer[nameof(ExportToastContent)];
|
||||
ExportToastInProgressContent ??= Localizer[nameof(ExportToastInProgressContent)];
|
||||
}
|
||||
}
|
||||
|
@ -174,6 +174,27 @@ public partial class Table<TItem>
|
||||
[Parameter]
|
||||
public string? EditDialogCloseButtonText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 导出数据弹窗 Title 默认为资源文件 导出数据
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[NotNull]
|
||||
public string? ExportToastTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 导出数据提示内容 默认为资源文件
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[NotNull]
|
||||
public string? ExportToastContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 正在导出数据提示内容 默认为资源文件
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
[NotNull]
|
||||
public string? ExportToastInProgressContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ToastService 服务实例
|
||||
/// </summary>
|
||||
@ -259,16 +280,14 @@ public partial class Table<TItem>
|
||||
{
|
||||
ShowAddForm = true;
|
||||
ShowEditForm = false;
|
||||
|
||||
await UpdateAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
else if (EditMode == EditMode.InCell)
|
||||
{
|
||||
AddInCell = true;
|
||||
EditInCell = true;
|
||||
SelectedRows.Add(EditModel);
|
||||
|
||||
await UpdateAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
await OnSelectedRowsChanged();
|
||||
await ToggleLoading(false);
|
||||
@ -324,15 +343,14 @@ public partial class Table<TItem>
|
||||
{
|
||||
ShowEditForm = true;
|
||||
ShowAddForm = false;
|
||||
await UpdateAsync();
|
||||
StateHasChanged();
|
||||
|
||||
}
|
||||
else if (EditMode == EditMode.InCell)
|
||||
{
|
||||
AddInCell = false;
|
||||
EditInCell = true;
|
||||
await UpdateAsync();
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
await ToggleLoading(false);
|
||||
}
|
||||
@ -498,7 +516,7 @@ public partial class Table<TItem>
|
||||
await ef.CancelAsync();
|
||||
await ToggleLoading(false);
|
||||
}
|
||||
await UpdateAsync();
|
||||
StateHasChanged();
|
||||
},
|
||||
OnEditAsync = async context =>
|
||||
{
|
||||
@ -515,18 +533,6 @@ public partial class Table<TItem>
|
||||
await DialogService.ShowEditDialog(option);
|
||||
}
|
||||
|
||||
private async Task UpdateAsync()
|
||||
{
|
||||
if (ItemsChanged.HasDelegate)
|
||||
{
|
||||
await ItemsChanged.InvokeAsync(RowItems);
|
||||
}
|
||||
else
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确认删除按钮方法
|
||||
/// </summary>
|
||||
@ -575,14 +581,14 @@ public partial class Table<TItem>
|
||||
{
|
||||
RowItems.RemoveAll(i => SelectedRows.Contains(i));
|
||||
SelectedRows.Clear();
|
||||
await UpdateAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
await ToggleLoading(true);
|
||||
var ret = await DelteItemsAsync();
|
||||
|
||||
if (ret && ShowToastAfterSaveOrDeleteModel && !IsTracking)
|
||||
if (ShowToastAfterSaveOrDeleteModel)
|
||||
{
|
||||
var option = new ToastOption()
|
||||
{
|
||||
@ -590,7 +596,6 @@ public partial class Table<TItem>
|
||||
Category = ret ? ToastCategory.Success : ToastCategory.Error
|
||||
};
|
||||
option.Content = string.Format(DeleteButtonToastResultContent, ret ? SuccessText : FailText, Math.Ceiling(option.Delay / 1000.0));
|
||||
|
||||
await Toast.Show(option);
|
||||
}
|
||||
await ToggleLoading(false);
|
||||
@ -600,6 +605,8 @@ public partial class Table<TItem>
|
||||
{
|
||||
var ret = await InternalOnDeleteAsync();
|
||||
if (ret)
|
||||
{
|
||||
if (IsPagination)
|
||||
{
|
||||
// 删除成功 重新查询
|
||||
// 由于数据删除导致页码会改变,尤其是最后一页
|
||||
@ -608,6 +615,7 @@ public partial class Table<TItem>
|
||||
PageIndex = Math.Max(1, Math.Min(PageIndex, int.Parse(Math.Ceiling((TotalCount - SelectedRows.Count) * 1d / PageItems).ToString())));
|
||||
var items = PageItemsSource.Where(item => item >= (TotalCount - SelectedRows.Count));
|
||||
PageItems = Math.Min(PageItems, items.Any() ? items.Min() : PageItems);
|
||||
}
|
||||
SelectedRows.Clear();
|
||||
await QueryAsync();
|
||||
}
|
||||
@ -646,38 +654,20 @@ public partial class Table<TItem>
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确认导出按钮方法
|
||||
/// </summary>
|
||||
protected async Task<bool> ConfirmExport()
|
||||
{
|
||||
var ret = false;
|
||||
if (!RowItems.Any())
|
||||
{
|
||||
var option = new ToastOption
|
||||
{
|
||||
Category = ToastCategory.Information,
|
||||
Title = "导出数据"
|
||||
};
|
||||
option.Content = $"没有需要导出的数据, {Math.Ceiling(option.Delay / 1000.0)} 秒后自动关闭";
|
||||
await Toast.Show(option);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出数据方法
|
||||
/// </summary>
|
||||
protected async Task ExportAsync()
|
||||
{
|
||||
var ret = false;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
var option = new ToastOption
|
||||
{
|
||||
Title = ExportToastTitle,
|
||||
Category = ToastCategory.Information
|
||||
};
|
||||
option.Content = string.Format(ExportToastInProgressContent, Math.Ceiling(option.Delay / 1000.0));
|
||||
await Toast.Show(option);
|
||||
|
||||
var ret = false;
|
||||
if (OnExportAsync != null)
|
||||
{
|
||||
ret = await OnExportAsync(RowItems);
|
||||
@ -689,26 +679,14 @@ public partial class Table<TItem>
|
||||
ret = await ExcelExport.ExportAsync(RowItems, Columns, JSRuntime);
|
||||
}
|
||||
|
||||
var option = new ToastOption()
|
||||
option = new ToastOption
|
||||
{
|
||||
Title = "导出数据"
|
||||
Title = ExportToastTitle,
|
||||
Category = ret ? ToastCategory.Success : ToastCategory.Error
|
||||
};
|
||||
option.Category = ret ? ToastCategory.Success : ToastCategory.Error;
|
||||
option.Content = $"导出数据{(ret ? "成功" : "失败")}, {Math.Ceiling(option.Delay / 1000.0)} 秒后自动关闭";
|
||||
|
||||
//$"导出数据{(ret ? "成功" : "失败")}, {Math.Ceiling(option.Delay / 1000.0)} 秒后自动关闭";
|
||||
option.Content = string.Format(ExportToastContent, ret ? SuccessText : FailText, Math.Ceiling(option.Delay / 1000.0));
|
||||
await Toast.Show(option);
|
||||
});
|
||||
|
||||
var option = new ToastOption()
|
||||
{
|
||||
Title = "导出数据"
|
||||
};
|
||||
option.Category = ToastCategory.Information;
|
||||
option.Content = $"正在导出数据,请稍候, {Math.Ceiling(option.Delay / 1000.0)} 秒后自动关闭";
|
||||
|
||||
await Toast.Show(option);
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -483,12 +483,6 @@ public partial class Table<TItem> : BootstrapComponentBase, IDisposable, ITable
|
||||
[Parameter]
|
||||
public IEnumerable<TItem>? Items { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 数据集合回调方法
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback<IEnumerable<TItem>> ItemsChanged { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 表格组件大小 默认为 Normal 正常模式
|
||||
/// </summary>
|
||||
|
@ -248,7 +248,10 @@
|
||||
"UnsetText": "Aufsteigend",
|
||||
"SortAscText": "Absteigend",
|
||||
"SortDescText": "Keine",
|
||||
"EmptyText": "Keine Daten"
|
||||
"EmptyText": "Keine Daten",
|
||||
"ExportToastTitle": "Daten exportieren",
|
||||
"ExportToastContent": "Daten exportieren {0}, Automatisches Schließen in {1} Sekunden",
|
||||
"ExportToastInProgressContent": "Daten exportieren, Automatisches Schließen in {0} Sekunden"
|
||||
},
|
||||
"BootstrapBlazor.Components.EditDialog": {
|
||||
"CloseButtonText": "Schließen",
|
||||
|
@ -248,7 +248,10 @@
|
||||
"UnsetText": "Asc",
|
||||
"SortAscText": "Desc",
|
||||
"SortDescText": "None",
|
||||
"EmptyText": "No Data"
|
||||
"EmptyText": "No Data",
|
||||
"ExportToastTitle": "Export",
|
||||
"ExportToastContent": "Export data {0}, auto close after {1}s",
|
||||
"ExportToastInProgressContent": "Exporting data, please waiting a moment, auto close after {0}s"
|
||||
},
|
||||
"BootstrapBlazor.Components.EditDialog": {
|
||||
"CloseButtonText": "Close",
|
||||
|
@ -248,7 +248,10 @@
|
||||
"UnsetText": "Asc",
|
||||
"SortAscText": "Desc",
|
||||
"SortDescText": "Nenhum",
|
||||
"EmptyText": "Vazio"
|
||||
"EmptyText": "Vazio",
|
||||
"ExportToastTitle": "Exportar dados",
|
||||
"ExportToastContent": "Exportar dados {0}, Fechamento automático em {0} segundos",
|
||||
"ExportToastInProgressContent": "Exportar dados, Fechamento automático em {0} segundos"
|
||||
},
|
||||
"BootstrapBlazor.Components.EditDialog": {
|
||||
"CloseButtonText": "Fechar",
|
||||
|
@ -248,7 +248,10 @@
|
||||
"UnsetText": "点击升序",
|
||||
"SortAscText": "点击降序",
|
||||
"SortDescText": "取消排序",
|
||||
"EmptyText": "无数据"
|
||||
"EmptyText": "无数据",
|
||||
"ExportToastTitle": "导出数据",
|
||||
"ExportToastContent": "导出数据 {0},{1} 秒后自动关闭",
|
||||
"ExportToastInProgressContent": "正在导出数据,请稍后, {0} 秒后自动关闭"
|
||||
},
|
||||
"BootstrapBlazor.Components.EditDialog": {
|
||||
"CloseButtonText": "关闭",
|
||||
|
File diff suppressed because one or more lines are too long
@ -3503,7 +3503,6 @@ public class TableTest : TableTestBase
|
||||
Assert.DoesNotContain("fa fa-remove", table.Find("tbody").ToMarkup());
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void ShowExtendEditButton_Ok()
|
||||
{
|
||||
@ -3537,6 +3536,14 @@ public class TableTest : TableTestBase
|
||||
pb.Add(a => a.ShowExtendEditButton, false);
|
||||
});
|
||||
Assert.DoesNotContain("fa fa-edit", table.Find("tbody").ToMarkup());
|
||||
|
||||
table.SetParametersAndRender(pb =>
|
||||
{
|
||||
pb.Add(a => a.ShowExtendEditButton, true);
|
||||
pb.Add(a => a.ShowDefaultButtons, false);
|
||||
pb.Add(a => a.ShowEditButtonCallback, foo => true);
|
||||
});
|
||||
Assert.Contains("fa fa-edit", table.Find("tbody").ToMarkup());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -3571,6 +3578,14 @@ public class TableTest : TableTestBase
|
||||
pb.Add(a => a.ShowExtendDeleteButton, false);
|
||||
});
|
||||
Assert.DoesNotContain("fa fa-remove", table.Find("tbody").ToMarkup());
|
||||
|
||||
table.SetParametersAndRender(pb =>
|
||||
{
|
||||
pb.Add(a => a.ShowExtendDeleteButton, true);
|
||||
pb.Add(a => a.ShowDefaultButtons, false);
|
||||
pb.Add(a => a.ShowDeleteButtonCallback, foo => true);
|
||||
});
|
||||
Assert.Contains("fa fa-remove", table.Find("tbody").ToMarkup());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -3658,6 +3673,124 @@ public class TableTest : TableTestBase
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConfirmDelete_Ok()
|
||||
{
|
||||
var localizer = Context.Services.GetRequiredService<IStringLocalizer<Foo>>();
|
||||
var items = Foo.GenerateFoo(localizer, 2);
|
||||
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
|
||||
{
|
||||
pb.AddChildContent<Table<Foo>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.RenderMode, TableRenderMode.Table);
|
||||
pb.Add(a => a.Items, items);
|
||||
pb.Add(a => a.IsMultipleSelect, true);
|
||||
pb.Add(a => a.ShowToolbar, true);
|
||||
pb.Add(a => a.TableColumns, foo => builder =>
|
||||
{
|
||||
builder.OpenComponent<TableColumn<Foo, string>>(0);
|
||||
builder.AddAttribute(1, "Field", "Name");
|
||||
builder.AddAttribute(2, "FieldExpression", Utility.GenerateValueExpression(foo, "Name", typeof(string)));
|
||||
builder.CloseComponent();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var table = cut.FindComponent<Table<Foo>>();
|
||||
var deleteButton = table.FindComponent<TableToolbarPopconfirmButton<Foo>>();
|
||||
await cut.InvokeAsync(() => deleteButton.Instance.OnBeforeClick());
|
||||
|
||||
// 选一个
|
||||
var input = cut.Find("tbody tr input");
|
||||
await cut.InvokeAsync(() => input.Click());
|
||||
await cut.InvokeAsync(() => deleteButton.Instance.OnBeforeClick());
|
||||
|
||||
table.SetParametersAndRender(pb =>
|
||||
{
|
||||
pb.Add(a => a.ShowDeleteButtonCallback, foo => false);
|
||||
});
|
||||
await cut.InvokeAsync(() => deleteButton.Instance.OnBeforeClick());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OnConfirm_Ok()
|
||||
{
|
||||
var localizer = Context.Services.GetRequiredService<IStringLocalizer<Foo>>();
|
||||
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
|
||||
{
|
||||
pb.AddChildContent<Table<Foo>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.RenderMode, TableRenderMode.Table);
|
||||
pb.Add(a => a.OnQueryAsync, OnQueryAsync(localizer));
|
||||
pb.Add(a => a.IsMultipleSelect, true);
|
||||
pb.Add(a => a.ShowToolbar, true);
|
||||
pb.Add(a => a.IsPagination, true);
|
||||
pb.Add(a => a.PageItemsSource, new int[] { 1 });
|
||||
pb.Add(a => a.TableColumns, foo => builder =>
|
||||
{
|
||||
builder.OpenComponent<TableColumn<Foo, string>>(0);
|
||||
builder.AddAttribute(1, "Field", "Name");
|
||||
builder.AddAttribute(2, "FieldExpression", Utility.GenerateValueExpression(foo, "Name", typeof(string)));
|
||||
builder.CloseComponent();
|
||||
});
|
||||
pb.Add(a => a.OnDeleteAsync, foos => Task.FromResult(true));
|
||||
});
|
||||
});
|
||||
|
||||
var table = cut.FindComponent<Table<Foo>>();
|
||||
var deleteButton = table.FindComponent<TableToolbarPopconfirmButton<Foo>>();
|
||||
// 选一个
|
||||
var input = cut.Find("tbody tr input");
|
||||
await cut.InvokeAsync(() => input.Click());
|
||||
await cut.InvokeAsync(() => deleteButton.Instance.OnConfirm());
|
||||
|
||||
table.SetParametersAndRender(pb =>
|
||||
{
|
||||
pb.Add(a => a.PageItemsSource, new int[] { 1, 2, 4, 8 });
|
||||
});
|
||||
await cut.InvokeAsync(() => deleteButton.Instance.OnConfirm());
|
||||
|
||||
table.SetParametersAndRender(pb =>
|
||||
{
|
||||
pb.Add(a => a.IsTracking, true);
|
||||
});
|
||||
await cut.InvokeAsync(() => deleteButton.Instance.OnConfirm());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ExportAsync_Ok()
|
||||
{
|
||||
var localizer = Context.Services.GetRequiredService<IStringLocalizer<Foo>>();
|
||||
var cut = Context.RenderComponent<BootstrapBlazorRoot>(pb =>
|
||||
{
|
||||
pb.AddChildContent<Table<Foo>>(pb =>
|
||||
{
|
||||
pb.Add(a => a.RenderMode, TableRenderMode.Table);
|
||||
pb.Add(a => a.OnQueryAsync, OnQueryAsync(localizer));
|
||||
pb.Add(a => a.ShowToolbar, true);
|
||||
pb.Add(a => a.ShowExportButton, true);
|
||||
pb.Add(a => a.TableColumns, foo => builder =>
|
||||
{
|
||||
builder.OpenComponent<TableColumn<Foo, string>>(0);
|
||||
builder.AddAttribute(1, "Field", "Name");
|
||||
builder.AddAttribute(2, "FieldExpression", Utility.GenerateValueExpression(foo, "Name", typeof(string)));
|
||||
builder.CloseComponent();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var button = cut.Find(".dropdown-menu-right .dropdown-item");
|
||||
await cut.InvokeAsync(() => button.Click());
|
||||
|
||||
//
|
||||
var table = cut.FindComponent<Table<Foo>>();
|
||||
table.SetParametersAndRender(pb =>
|
||||
{
|
||||
pb.Add(a => a.OnExportAsync, foos => Task.FromResult(true));
|
||||
});
|
||||
await cut.InvokeAsync(() => button.Click());
|
||||
}
|
||||
|
||||
private static DataTable CreateDataTable(IStringLocalizer<Foo> localizer)
|
||||
{
|
||||
var userData = new DataTable();
|
||||
|
Loading…
Reference in New Issue
Block a user