mirror of
https://gitee.com/LongbowEnterprise/BootstrapBlazor.git
synced 2025-12-06 07:28:50 +08:00
perf(Message): improve performance (#7094)
* doc: 更新示例 * chore: 更新字典 * refactor: 精简代码 * refactor: 移除 Clear 方法参数传递 * chore: 增加字典 * feat: 支持全局配置自动隐藏时长设置 * doc: 更新注释 * feat: 精简反转逻辑 * refactor: 更新 Clear 方法 * chore: 增加命名空间 * refactor: 更新 init 接口参数 * refactor: 重构 Dismiss 逻辑提高性能 * fix: 修复异步消息未清除 dom 问题 * refactor: 更新关闭逻辑 * refactor: 移除 click 事件委托 * refactor: 精简代码 * test: 更新单元测试 * refactor: 精简代码 * perf: 提高性能
This commit is contained in:
@@ -121,3 +121,5 @@ dotx
|
||||
Modbus
|
||||
Protocol
|
||||
vditor
|
||||
alertdialog
|
||||
blazorbootstrap
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@page "/message"
|
||||
@page "/message"
|
||||
@inject IStringLocalizer<Messages> Localizer
|
||||
@inject MessageService MessageService
|
||||
|
||||
@@ -20,8 +20,18 @@ private MessageService? MessageService { get; set; }
|
||||
});</Pre>
|
||||
|
||||
<DemoBlock Title="@Localizer["MessagesNormalTitle"]" Introduction="@Localizer["MessagesNormalIntro"]" Name="Normal">
|
||||
<button class="btn btn-primary" @onclick="@ShowMessage">@Localizer["MessagesMessagePrompt"]</button>
|
||||
<Message @ref="Message" Placement="Placement.Bottom" />
|
||||
<section ignore class="row form-inline g-3">
|
||||
<div class="col-12">
|
||||
<BootstrapInputGroup>
|
||||
<BootstrapInputGroupLabel DisplayText="Placement"></BootstrapInputGroupLabel>
|
||||
<RadioList @bind-Value="@_placement" Items="@_items"></RadioList>
|
||||
</BootstrapInputGroup>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" @onclick="@ShowMessage">@Localizer["MessagesMessagePrompt"]</button>
|
||||
</div>
|
||||
</section>
|
||||
<Message @ref="Message" Placement="@Placement"></Message>
|
||||
</DemoBlock>
|
||||
|
||||
<DemoBlock Title="@Localizer["MessagesAsyncTitle"]" Introduction="@Localizer["MessagesAsyncIntro"]" Name="Async">
|
||||
@@ -70,7 +80,6 @@ private MessageService? MessageService { get; set; }
|
||||
|
||||
<DemoBlock Title="@Localizer["MessagesTemplateTitle"]" Introduction="@Localizer["MessagesTemplateIntro"]" Name="Template">
|
||||
<button class="btn btn-primary" @onclick="@ShowTemplateMessage">@Localizer["MessagesTemplatePrompt"]</button>
|
||||
<Message @ref="Message1" Placement="Placement.Bottom" />
|
||||
</DemoBlock>
|
||||
|
||||
<DemoBlock Title="@Localizer["MessagesShowModeTitle"]" Introduction="@Localizer["MessagesShowModeIntro"]" Name="ShowMode">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
@@ -18,13 +18,20 @@ public sealed partial class Messages
|
||||
|
||||
private readonly MessageOption _option = new();
|
||||
|
||||
private long _count = 0;
|
||||
|
||||
private string _placement = "Top";
|
||||
|
||||
private readonly List<SelectedItem> _items = [new SelectedItem("Top", "Top"), new SelectedItem("Bottom", "Bottom")];
|
||||
|
||||
private Placement Placement => _placement == "Top" ? Placement.Top : Placement.Bottom;
|
||||
|
||||
private async Task ShowMessage()
|
||||
{
|
||||
Message.SetPlacement(Placement.Top);
|
||||
await MessageService.Show(new MessageOption()
|
||||
{
|
||||
Content = "This is a reminder message"
|
||||
});
|
||||
Content = $"This is a reminder message {_count++}"
|
||||
}, Message);
|
||||
}
|
||||
|
||||
private async Task ShowAsyncMessage()
|
||||
@@ -97,7 +104,7 @@ public sealed partial class Messages
|
||||
{
|
||||
await MessageService.Show(new MessageOption()
|
||||
{
|
||||
Content = $"This is a reminder message - {DateTime.Now:mm:ss}",
|
||||
Content = $"This is a reminder message - {_count++}",
|
||||
Icon = "fa-solid fa-circle-info",
|
||||
}, Message1);
|
||||
}
|
||||
@@ -111,13 +118,11 @@ public sealed partial class Messages
|
||||
});
|
||||
}
|
||||
|
||||
private int lastCount = 0;
|
||||
|
||||
private Task ShowLastOnlyMessage() => MessageService.Show(new MessageOption()
|
||||
{
|
||||
ShowShadow = true,
|
||||
ShowMode = MessageShowMode.Single,
|
||||
Content = lastCount++.ToString()
|
||||
Content = $"This is a reminder message - {_count++}"
|
||||
});
|
||||
|
||||
private static AttributeItem[] GetAttributes() =>
|
||||
|
||||
@@ -1,59 +1,29 @@
|
||||
@namespace BootstrapBlazor.Components
|
||||
@namespace BootstrapBlazor.Components
|
||||
@inherits BootstrapModuleComponentBase
|
||||
@attribute [BootstrapModuleAutoLoader(JSObjectReference = true)]
|
||||
|
||||
<div id="@Id" class="@ClassString" style="@StyleName" role="alert">
|
||||
@if (Placement == Placement.Top)
|
||||
@foreach (var item in MessagesForRender)
|
||||
{
|
||||
foreach (var item in _messages)
|
||||
{
|
||||
<div @key="item" id="@GetItemId(item)" role="alertdialog" class="@GetItemClassString(item)" data-bb-autohide="@GetAutoHideString(item)" data-bb-delay="@item.Delay">
|
||||
@if (!string.IsNullOrEmpty(item.Icon))
|
||||
<div @key="item" id="@GetItemId(item)" role="alertdialog" class="@GetItemClassString(item)" data-bb-autohide="@GetAutoHideString(item)" data-bb-delay="@item.Delay">
|
||||
@if (!string.IsNullOrEmpty(item.Icon))
|
||||
{
|
||||
<i class="@item.Icon"></i>
|
||||
}
|
||||
<div>
|
||||
@if (item.ChildContent != null)
|
||||
{
|
||||
<i class="@item.Icon"></i>
|
||||
@item.ChildContent
|
||||
}
|
||||
<div>
|
||||
@if (item.ChildContent != null)
|
||||
{
|
||||
@item.ChildContent
|
||||
}
|
||||
else
|
||||
{
|
||||
@item.Content
|
||||
}
|
||||
</div>
|
||||
@if (item.ShowDismiss)
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn-close" aria-label="close"></button>
|
||||
@item.Content
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var index = _messages.Count; index > 0; index--)
|
||||
{
|
||||
var item = _messages[index - 1];
|
||||
<div @key="item" id="@GetItemId(item)" role="alertdialog" class="@GetItemClassString(item)" data-bb-autohide="@GetAutoHideString(item)" data-bb-delay="@item.Delay">
|
||||
@if (!string.IsNullOrEmpty(item.Icon))
|
||||
{
|
||||
<i class="@item.Icon"></i>
|
||||
}
|
||||
<div>
|
||||
@if (item.ChildContent != null)
|
||||
{
|
||||
@item.ChildContent
|
||||
}
|
||||
else
|
||||
{
|
||||
@item.Content
|
||||
}
|
||||
</div>
|
||||
@if (item.ShowDismiss)
|
||||
{
|
||||
<button type="button" class="btn-close" aria-label="close"></button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (item.ShowDismiss)
|
||||
{
|
||||
<button type="button" class="btn-close" aria-label="close"></button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
@@ -27,6 +27,10 @@ public partial class Message
|
||||
|
||||
private readonly List<MessageOption> _messages = [];
|
||||
|
||||
private IEnumerable<MessageOption> MessagesForRender => Placement == Placement.Bottom
|
||||
? _messages.AsEnumerable().Reverse()
|
||||
: _messages;
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 显示位置 默认为 Top
|
||||
/// </summary>
|
||||
@@ -55,7 +59,7 @@ public partial class Message
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(Clear));
|
||||
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop);
|
||||
|
||||
private static string? GetAutoHideString(MessageOption option) => option.IsAutoHide ? "true" : null;
|
||||
|
||||
@@ -86,7 +90,7 @@ public partial class Message
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置 Toast 容器位置方法
|
||||
/// 设置 容器位置方法
|
||||
/// </summary>
|
||||
/// <param name="placement"></param>
|
||||
public void SetPlacement(Placement placement)
|
||||
@@ -105,8 +109,8 @@ public partial class Message
|
||||
if (!_messages.Contains(option))
|
||||
{
|
||||
_messages.Add(option);
|
||||
_msgId = GetItemId(option);
|
||||
}
|
||||
_msgId = GetItemId(option);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
@@ -114,11 +118,15 @@ public partial class Message
|
||||
/// 清除 Message 方法 由 JSInvoke 触发
|
||||
/// </summary>
|
||||
[JSInvokable]
|
||||
public Task Clear()
|
||||
public void Clear(string id)
|
||||
{
|
||||
_messages.Clear();
|
||||
var option = _messages.Find(i => GetItemId(i) == id);
|
||||
if (option != null)
|
||||
{
|
||||
_messages.Remove(option);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -126,12 +134,14 @@ public partial class Message
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
[JSInvokable]
|
||||
public async Task Dismiss(string id)
|
||||
public async ValueTask Dismiss(string id)
|
||||
{
|
||||
var option = _messages.Find(i => GetItemId(i) == id);
|
||||
if (option is { OnDismiss: not null })
|
||||
{
|
||||
await option.OnDismiss();
|
||||
_messages.Remove(option);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import Data from "../../modules/data.js"
|
||||
import Data from "../../modules/data.js"
|
||||
import EventHandler from "../../modules/event-handler.js"
|
||||
|
||||
export function init(id, invoke, callback) {
|
||||
export function init(id, invoke) {
|
||||
const el = document.getElementById(id)
|
||||
const msg = { el, invoke, callback, items: [] }
|
||||
const msg = { el, invoke, items: [] }
|
||||
Data.set(id, msg)
|
||||
}
|
||||
|
||||
export function show(id, msgId) {
|
||||
const msg = Data.get(id)
|
||||
const el = document.getElementById(msgId)
|
||||
if (el === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const msg = Data.get(id)
|
||||
let msgItem = msg.items.find(i => i.el.id === msgId)
|
||||
if (msgItem === void 0) {
|
||||
msgItem = { el, animationId: null }
|
||||
@@ -21,7 +21,7 @@ export function show(id, msgId) {
|
||||
}
|
||||
|
||||
if (msgItem.animationId) {
|
||||
cancelAnimationFrame(msgItem.animationId);
|
||||
return;
|
||||
}
|
||||
|
||||
const autoHide = el.getAttribute('data-bb-autohide') === 'true';
|
||||
@@ -46,28 +46,25 @@ export function show(id, msgId) {
|
||||
el.classList.add('show');
|
||||
|
||||
const close = () => {
|
||||
EventHandler.off(el, 'click')
|
||||
el.classList.remove('show');
|
||||
const hideHandler = setTimeout(function () {
|
||||
clearTimeout(hideHandler);
|
||||
el.classList.add("d-none");
|
||||
|
||||
msg.items.pop();
|
||||
if (msg.items.length === 0) {
|
||||
msg.invoke.invokeMethodAsync(msg.callback);
|
||||
}
|
||||
}, 500);
|
||||
msg.items = msg.items.filter(i => i.el.id !== msgId);
|
||||
msg.invoke.invokeMethodAsync("Clear", msgId);
|
||||
};
|
||||
|
||||
EventHandler.on(el, 'click', '.btn-close', e => {
|
||||
EventHandler.on(el, 'click', '.btn-close', async e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const alert = e.delegateTarget.closest('.alert');
|
||||
if (alert) {
|
||||
EventHandler.off(el, 'click')
|
||||
alert.classList.add("d-none");
|
||||
|
||||
const alertId = alert.getAttribute('id');
|
||||
msg.invoke.invokeMethodAsync('Dismiss', alertId);
|
||||
msg.items = msg.items.filter(i => i.el.id !== alertId);
|
||||
await msg.invoke.invokeMethodAsync('Dismiss', alertId);
|
||||
}
|
||||
close();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the Apache 2.0 License
|
||||
// See the LICENSE file in the project root for more information.
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace UnitTest.Components;
|
||||
|
||||
@@ -57,10 +58,8 @@ public class MessageTest : BootstrapBlazorTestBase
|
||||
Assert.NotNull(alert.Id);
|
||||
|
||||
var message = cut.FindComponent<Message>();
|
||||
await message.Instance.Dismiss(alert.Id);
|
||||
await cut.InvokeAsync(() => message.Instance.Dismiss(alert.Id));
|
||||
Assert.True(dismiss);
|
||||
|
||||
await cut.InvokeAsync(() => message.Instance.Clear());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -98,7 +97,7 @@ public class MessageTest : BootstrapBlazorTestBase
|
||||
|
||||
await cut.Instance.Dismiss(alert.Id);
|
||||
await cut.Instance.Dismiss("test_id");
|
||||
await cut.InvokeAsync(() => cut.Instance.Clear());
|
||||
await cut.InvokeAsync(() => cut.Instance.Clear(alert.Id));
|
||||
|
||||
await cut.InvokeAsync(() => service.Show(new MessageOption()
|
||||
{
|
||||
@@ -133,4 +132,44 @@ public class MessageTest : BootstrapBlazorTestBase
|
||||
ShowMode = MessageShowMode.Single
|
||||
}, cut.Instance));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ForceDelay_Ok()
|
||||
{
|
||||
var service = Context.Services.GetRequiredService<MessageService>();
|
||||
var cut = Context.RenderComponent<Message>();
|
||||
var option = new MessageOption()
|
||||
{
|
||||
Content = "Test Content",
|
||||
IsAutoHide = false,
|
||||
ShowDismiss = true,
|
||||
Icon = "fa-solid fa-font-awesome",
|
||||
ForceDelay = true,
|
||||
Delay = 2000
|
||||
};
|
||||
await cut.InvokeAsync(() => service.Show(option, cut.Instance));
|
||||
Assert.Contains("data-bb-delay=\"2000\"", cut.Markup);
|
||||
|
||||
var alert = cut.Find(".alert");
|
||||
Assert.NotNull(alert);
|
||||
Assert.NotNull(alert.Id);
|
||||
await cut.InvokeAsync(() => cut.Instance.Clear(alert.Id));
|
||||
|
||||
option.ForceDelay = false;
|
||||
await cut.InvokeAsync(() => service.Show(option, cut.Instance));
|
||||
Assert.Contains("data-bb-delay=\"4000\"", cut.Markup);
|
||||
await cut.InvokeAsync(() => cut.Instance.Clear(alert.Id));
|
||||
|
||||
// 更新 Options 值
|
||||
var options = Context.Services.GetRequiredService<IOptionsMonitor<BootstrapBlazorOptions>>();
|
||||
options.CurrentValue.MessageDelay = 1000;
|
||||
await cut.InvokeAsync(() => service.Show(option, cut.Instance));
|
||||
Assert.Contains("data-bb-delay=\"1000\"", cut.Markup);
|
||||
await cut.InvokeAsync(() => cut.Instance.Clear(alert.Id));
|
||||
|
||||
options.CurrentValue.MessageDelay = 0;
|
||||
await cut.InvokeAsync(() => service.Show(option, cut.Instance));
|
||||
Assert.Contains("data-bb-delay=\"1000\"", cut.Markup);
|
||||
await cut.InvokeAsync(() => cut.Instance.Clear(alert.Id));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user