!3532 fix(#I619LS): redesign console component

* doc: 增加 HeaderTemplate 说明文档
* test: 增加 HeaderTemplate 单元测试
* feat: 增加 HeaderTemplate 参数
* doc: 更新 Console 参数文档
* test: 删除冗余单元测试
* test: 增加 IsFlashLight 单元测试
* test: 增加 LightColor 单元测试
* test: 增加 ShowLight 单元测试
* test: 增加 FooterTemplate 单元测试
* chore: 增加 IsFlashLight 参数控制是否闪烁
* feat: 增加 ShowLight 参数
* feat: 增加 LightColor 参数
* test: 增加 FooterTemplate 单元测试
* doc: 增加 FooterTemplate 说明文档
* feat: 增加 FooterTemplate 模板
* doc: 更新 Consoles 示例
* chore: 更改为动态加载脚本
* chore: 移除 ConsoleBase 基类
This commit is contained in:
Argo 2022-11-15 08:59:41 +00:00
parent 1a9d2f2e7d
commit 913cc65d16
14 changed files with 333 additions and 238 deletions

View File

@ -850,7 +850,9 @@
"P10": "Show autoscroll options",
"P11": "by setting",
"P12": "Turn on autoscroll",
"P13": "ConsoleMessageItem property"
"P13": "ConsoleMessageItem property",
"ShowAutoScrollTitle": "ShowAutoScroll",
"ShowAutoScrollIntro": "Switch auto scroll the data by set <code>ShowAutoScroll</code> parameter"
},
"BootstrapBlazor.Shared.Samples.Synthesizers": {
"H1": "Synthesizer speech synthesis",

View File

@ -851,7 +851,9 @@
"P10": "显示自动滚屏选项",
"P11": "通过设置",
"P12": "设置开启自动滚屏",
"P13": "ConsoleMessageItem 属性"
"P13": "ConsoleMessageItem 属性",
"ShowAutoScrollTitle": "自动滚屏控制",
"ShowAutoScrollIntro": "通过设置 <code>ShowAutoScroll</code> 显示 <code>Checkbox</code> 控制是否自动滚动屏幕"
},
"BootstrapBlazor.Shared.Samples.Synthesizers": {
"H1": "Synthesizer 语音合成",

View File

@ -5,25 +5,27 @@
<h4>@Localizer["H2"]</h4>
<DemoBlock Title="@Localizer["P1"]" Introduction="@Localizer["P2"]" Name="Normal">
<Console Items="@Messages" Height="126" />
</DemoBlock>
<DemoBlock Title="@Localizer["P3"]" Introduction="@Localizer["P4"]" Name="OnClear">
<Console Items="@Messages" Height="126" OnClear="@OnClear" />
<Console Items="@Messages" Height="126" IsAutoScroll="false" />
</DemoBlock>
<DemoBlock Title="@Localizer["P5"]" Introduction="@Localizer["P6"]" Name="Color">
<Console Items="@ColorMessages" Height="126" />
</DemoBlock>
<DemoBlock Title="@Localizer["P7"]" Introduction="@Localizer["P8"]" Name="AutoScroll">
<p>@Localizer["P9"] <code>ShowAutoScroll="true"</code> @Localizer["P10"]</p>
<Console Items="@Messages" Height="126" ShowAutoScroll="true" />
<DemoBlock Title="@Localizer["P7"]" Introduction="@Localizer["P8"]" Name="IsAutoScroll">
<p class="mt-3">@Localizer["P11"] <code>IsAutoScroll</code> @Localizer["P12"]</p>
<Console Items="@Messages" Height="126" IsAutoScroll="true" />
</DemoBlock>
<DemoBlock Title="@Localizer["P3"]" Introduction="@Localizer["P4"]" Name="OnClear">
<Console Items="@Messages" Height="126" OnClear="@OnClear" />
</DemoBlock>
<DemoBlock Title="@Localizer["ShowAutoScrollTitle"]" Introduction="@Localizer["ShowAutoScrollIntro"]" Name="ShowAutoScroll">
<p>@Localizer["P9"] <code>ShowAutoScroll="true"</code> @Localizer["P10"]</p>
<Console Items="@Messages" Height="126" ShowAutoScroll="true" OnClear="@OnClear" />
</DemoBlock>
<AttributeTable Items="@GetAttributes()" />
<AttributeTable Items="@GetItemAttributes()" Title="@Localizer["P13"]" />

View File

@ -149,6 +149,13 @@ public sealed partial class Consoles : IDisposable
ValueList = " — ",
DefaultValue = "系统监控"
},
new AttributeItem(){
Name = "HeaderTemplate",
Description = "Header 模板",
Type = "RenderFragment",
ValueList = " — ",
DefaultValue = " — "
},
new AttributeItem(){
Name = "LightTitle",
Description = "指示灯 Title",
@ -156,6 +163,27 @@ public sealed partial class Consoles : IDisposable
ValueList = " — ",
DefaultValue = "通讯指示灯"
},
new AttributeItem(){
Name = "IsFlashLight",
Description = "指示灯是否闪烁",
Type = "bool",
ValueList = "true/false",
DefaultValue = "true"
},
new AttributeItem(){
Name = "LightColor",
Description = "指示灯颜色",
Type = "Color",
ValueList = " — ",
DefaultValue = "Color.Success"
},
new AttributeItem(){
Name = "ShowLight",
Description = "是否显示指示灯",
Type = "bool",
ValueList = "true/false",
DefaultValue = "true"
},
new AttributeItem(){
Name = "ClearButtonText",
Description = "按钮显示文字",
@ -176,6 +204,13 @@ public sealed partial class Consoles : IDisposable
Type = "Color",
ValueList = "None / Active / Primary / Secondary / Success / Danger / Warning / Info / Light / Dark / Link",
DefaultValue = "Secondary"
},
new AttributeItem(){
Name = "FooterTemplate",
Description = "Footer 模板",
Type = "RenderFragment",
ValueList = " — ",
DefaultValue = " — "
}
};

View File

@ -1,6 +1,19 @@
.console-body {
padding: 1rem;
background-color: #174482;
color: #fff;
overflow: auto;
.console {
--bs-card-color: #fff;
--bb-console-body-bg: #174482;
}
.console .card-body {
background-color: var(--bb-console-body-bg);
overflow: auto;
}
.console .card-footer {
display: flex;
align-items: center;
justify-content: end;
}
.console .card-footer .console-clear {
margin-left: .5rem;
}

View File

@ -1,12 +0,0 @@
(function ($) {
$.extend({
bb_console: function (el) {
var $el = $(el);
var $body = $el.find('[data-scroll="auto"]');
if ($body.length > 0) {
var $win = $body.find('.console-window');
$body.scrollTop($win.height());
}
}
});
})(jQuery);

View File

@ -1,12 +1,22 @@
@namespace BootstrapBlazor.Components
@inherits ConsoleBase
@inherits BootstrapModuleComponentBase
<div @attributes="@AdditionalAttributes" class="@ClassString" @ref="@ConsoleElement">
<div @attributes="@AdditionalAttributes" class="@ClassString" id="@Id" data-bb-scroll="@AutoScrollString">
<div class="card-header d-flex">
<span class="flex-fill">@HeaderText</span>
<Light IsFlash="true" TooltipText="@LightTitle" Color="@Color.Success"></Light>
@if (HeaderTemplate != null)
{
@HeaderTemplate
}
else
{
<span class="flex-fill">@HeaderText</span>
@if (ShowLight)
{
<Light IsFlash="@IsFlashLight" TooltipText="@LightTitle" Color="@LightColor"></Light>
}
}
</div>
<div class="card-body console-body" data-scroll="@AutoScrollString" style="@BodyStyleString">
<div class="card-body" style="@BodyStyleString">
<div class="console-window">
@foreach (var item in Items)
{
@ -14,17 +24,21 @@
}
</div>
</div>
<div class="@FooterClassString">
@if (ShowAutoScroll)
{
<Checkbox @bind-Value="@IsAutoScroll" ShowAfterLabel="true" DisplayText="@AutoScrollText" />
}
@if (OnClear != null)
{
<button type="button" class="@ClearButtonClassString" @onclick="@ClearConsole">
<i class="@ClearButtonIcon"></i>
<span>@ClearButtonText</span>
</button>
}
</div>
@if (ShowFooter)
{
<div class="card-footer">
@if (FooterTemplate != null)
{
@FooterTemplate
}
else if (ShowAutoScroll)
{
<Checkbox @bind-Value="@IsAutoScroll" ShowAfterLabel="true" DisplayText="@AutoScrollText" />
}
@if (OnClear != null)
{
<Button Text="@ClearButtonText" Icon="@ClearButtonIcon" Color="@ClearButtonColor" OnClick="ClearConsole" class="console-clear" />
}
</div>
}
</div>

View File

@ -3,12 +3,14 @@
// Website: https://www.blazor.zone or https://argozhang.github.io/
using Microsoft.Extensions.Localization;
using System.Reflection.Metadata;
namespace BootstrapBlazor.Components;
/// <summary>
/// 控制台消息组件
/// </summary>
[JSModuleAutoLoader]
public partial class Console
{
/// <summary>
@ -25,62 +27,6 @@ public partial class Console
.AddClass($"height: {Height}px;", Height > 0)
.Build();
/// <summary>
/// 获得 Footer 样式
/// </summary>
private string? FooterClassString => CssBuilder.Default("card-footer text-end")
.AddClass("d-none", OnClear == null && !ShowAutoScroll)
.Build();
/// <summary>
/// 获得 按钮样式
/// </summary>
private string? ClearButtonClassString => CssBuilder.Default("btn btn-secondary")
.AddClass($"btn-{ClearButtonColor.ToDescriptionString()}", ClearButtonColor != Color.None)
.Build();
/// <summary>
/// 获得 客户端是否自动滚屏标识
/// </summary>
private string? AutoScrollString => (IsAutoScroll || ShowAutoScroll) ? "auto" : null;
/// <summary>
/// 获得 Console 组件客户端引用实例
/// </summary>
private ElementReference ConsoleElement { get; set; }
[Inject]
[NotNull]
private IStringLocalizer<Console>? Localizer { get; set; }
/// <summary>
/// OnInitialized 方法
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
HeaderText ??= Localizer[nameof(HeaderText)];
LightTitle ??= Localizer[nameof(LightTitle)];
ClearButtonText ??= Localizer[nameof(ClearButtonText)];
AutoScrollText ??= Localizer[nameof(AutoScrollText)];
}
/// <summary>
/// OnAfterRenderAsync 方法
/// </summary>
/// <param name="firstRender"></param>
/// <returns></returns>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (IsAutoScroll)
{
await JSRuntime.InvokeVoidAsync(ConsoleElement, "bb_console");
}
}
/// <summary>
/// 获取消息样式
/// </summary>
@ -90,6 +36,140 @@ public partial class Console
.AddClass($"text-{item.Color.ToDescriptionString()}", item.Color != Color.None)
.Build();
/// <summary>
/// 获得 客户端是否自动滚屏标识
/// </summary>
private string? AutoScrollString => IsAutoScroll ? "auto" : null;
/// <summary>
/// 获得/设置 组件绑定数据源
/// </summary>
[Parameter]
[NotNull]
public IEnumerable<ConsoleMessageItem>? Items { get; set; }
/// <summary>
/// 获得/设置 Header 显示文字 默认值为 系统监控
/// </summary>
[Parameter]
public string? HeaderText { get; set; }
/// <summary>
/// 获得/设置 指示灯 Title 显示文字
/// </summary>
[Parameter]
public string? LightTitle { get; set; }
/// <summary>
/// 获得/设置 指示灯 是否闪烁 默认 true 闪烁
/// </summary>
[Parameter]
public bool IsFlashLight { get; set; } = true;
/// <summary>
/// 获得/设置 指示灯颜色
/// </summary>
[Parameter]
public Color LightColor { get; set; } = Color.Success;
/// <summary>
/// 获得/设置 是否显示指示灯 默认 true 显示
/// </summary>
[Parameter]
public bool ShowLight { get; set; } = true;
/// <summary>
/// 获得/设置 自动滚屏显示文字
/// </summary>
[Parameter]
public string? AutoScrollText { get; set; }
/// <summary>
/// 获得/设置 是否显示自动滚屏选项 默认 false
/// </summary>
[Parameter]
public bool ShowAutoScroll { get; set; }
/// <summary>
/// 获得/设置 是否自动滚屏 默认 true
/// </summary>
[Parameter]
public bool IsAutoScroll { get; set; } = true;
/// <summary>
/// 获得/设置 按钮 显示文字 默认值为 清屏
/// </summary>
[Parameter]
public string? ClearButtonText { get; set; }
/// <summary>
/// 获得/设置 按钮 显示图标 默认值为 fa-solid fa-xmark
/// </summary>
[Parameter]
[NotNull]
public string? ClearButtonIcon { get; set; }
/// <summary>
/// 获得/设置 按钮 显示图标 默认值为 fa-times
/// </summary>
[Parameter]
public Color ClearButtonColor { get; set; } = Color.Secondary;
/// <summary>
/// 获得/设置 清空委托方法
/// </summary>
[Parameter]
public Action? OnClear { get; set; }
/// <summary>
/// 获得/设置 组件高度 默认为 126px;
/// </summary>
[Parameter]
public int Height { get; set; }
/// <summary>
/// 获得/设置 Footer 模板
/// </summary>
[Parameter]
public RenderFragment? FooterTemplate { get; set; }
/// <summary>
/// 获得/设置 Header 模板
/// </summary>
[Parameter]
public RenderFragment? HeaderTemplate { get; set; }
/// <summary>
/// 获得 是否显示 Footer
/// </summary>
protected bool ShowFooter => OnClear != null || ShowAutoScroll || FooterTemplate != null;
[Inject]
[NotNull]
private IStringLocalizer<Console>? Localizer { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnParametersSet()
{
base.OnParametersSet();
HeaderText ??= Localizer[nameof(HeaderText)];
LightTitle ??= Localizer[nameof(LightTitle)];
ClearButtonText ??= Localizer[nameof(ClearButtonText)];
AutoScrollText ??= Localizer[nameof(AutoScrollText)];
ClearButtonIcon ??= "fa-solid fa-xmark";
Items ??= Enumerable.Empty<ConsoleMessageItem>();
}
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override Task ModuleExecuteAsync() => InvokeExecuteAsync(Id);
/// <summary>
/// 清空控制台消息方法
/// </summary>

View File

@ -1,77 +0,0 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
namespace BootstrapBlazor.Components;
/// <summary>
/// Console 组件
/// </summary>
public abstract class ConsoleBase : BootstrapComponentBase
{
/// <summary>
/// 获得/设置 组件绑定数据源
/// </summary>
[Parameter]
public IEnumerable<ConsoleMessageItem> Items { get; set; } = Enumerable.Empty<ConsoleMessageItem>();
/// <summary>
/// 获得/设置 Header 显示文字 默认值为 系统监控
/// </summary>
[Parameter]
public string? HeaderText { get; set; }
/// <summary>
/// 获得/设置 指示灯 Title 显示文字
/// </summary>
[Parameter]
public string? LightTitle { get; set; }
/// <summary>
/// 获得/设置 自动滚屏显示文字
/// </summary>
[Parameter]
public string? AutoScrollText { get; set; }
/// <summary>
/// 获得/设置 是否显示自动滚屏选项 默认 false
/// </summary>
[Parameter]
public bool ShowAutoScroll { get; set; }
/// <summary>
/// 获得/设置 是否自动滚屏 默认 true
/// </summary>
[Parameter]
public bool IsAutoScroll { get; set; } = true;
/// <summary>
/// 获得/设置 按钮 显示文字 默认值为 清屏
/// </summary>
[Parameter]
public string? ClearButtonText { get; set; }
/// <summary>
/// 获得/设置 按钮 显示图标 默认值为 fa-times
/// </summary>
[Parameter]
public string ClearButtonIcon { get; set; } = "fa-solid fa-xmark";
/// <summary>
/// 获得/设置 按钮 显示图标 默认值为 fa-times
/// </summary>
[Parameter]
public Color ClearButtonColor { get; set; } = Color.Secondary;
/// <summary>
/// 获得/设置 清空委托方法
/// </summary>
[Parameter]
public Action? OnClear { get; set; }
/// <summary>
/// 获得/设置 组件高度 默认为 126px;
/// </summary>
[Parameter]
public int Height { get; set; }
}

View File

@ -1292,19 +1292,6 @@
});
})(jQuery);
(function ($) {
$.extend({
bb_console: function (el) {
var $el = $(el);
var $body = $el.find('[data-scroll="auto"]');
if ($body.length > 0) {
var $win = $body.find('.console-window');
$body.scrollTop($win.height());
}
}
});
})(jQuery);
(function ($) {
$.extend({
bb_form_load: function (el, method) {

File diff suppressed because one or more lines are too long

View File

@ -19279,19 +19279,6 @@ return jQuery;
});
})(jQuery);
(function ($) {
$.extend({
bb_console: function (el) {
var $el = $(el);
var $body = $el.find('[data-scroll="auto"]');
if ($body.length > 0) {
var $win = $body.find('.console-window');
$body.scrollTop($win.height());
}
}
});
})(jQuery);
(function ($) {
$.extend({
bb_form_load: function (el, method) {

View File

@ -0,0 +1,16 @@
import BlazorComponent from "./base/blazor-component.js"
import { getHeight } from "./base/utility.js"
export class Console extends BlazorComponent {
_init() {
this._body = this._element.querySelector('.card-body')
this._window = this._element.querySelector('.console-window')
}
_execute() {
const scroll = this._element.getAttribute('data-bb-scroll') === 'auto'
if (scroll) {
this._body.scrollTo(0, getHeight(this._window))
}
}
}

View File

@ -131,23 +131,6 @@ public class ConsoleTest : BootstrapBlazorTestBase
Assert.Contains("form-check", cut.Markup);
}
[Fact]
public void IsAutoScroll_OK()
{
var cut = Context.RenderComponent<Console>(builder =>
{
builder.Add(a => a.Items, new List<ConsoleMessageItem>()
{
new ConsoleMessageItem() {Message = "Test1"}, new ConsoleMessageItem() {Message = "Test2"}
});
builder.Add(a => a.ShowAutoScroll, true);
builder.Add(a => a.IsAutoScroll, true);
});
var res = cut.Find(".console-body").GetAttribute("data-scroll");
Assert.Equal("auto", res);
}
[Fact]
public void AutoScrollString_OK()
{
@ -159,26 +142,13 @@ public class ConsoleTest : BootstrapBlazorTestBase
});
builder.Add(a => a.IsAutoScroll, true);
});
Assert.Contains("data-scroll=\"auto\"", cut.Markup);
Assert.Contains("data-bb-scroll=\"auto\"", cut.Markup);
cut.SetParametersAndRender(pb =>
{
pb.Add(a => a.IsAutoScroll, false);
});
Assert.DoesNotContain("data-scroll=\"auto\"", cut.Markup);
cut.SetParametersAndRender(pb =>
{
pb.Add(a => a.IsAutoScroll, false);
pb.Add(a => a.ShowAutoScroll, false);
});
Assert.DoesNotContain("data-scroll", cut.Markup);
cut.SetParametersAndRender(pb =>
{
pb.Add(a => a.ShowAutoScroll, true);
});
Assert.Contains("data-scroll=\"auto\"", cut.Markup);
Assert.DoesNotContain("data-bb-scroll=\"auto\"", cut.Markup);
}
[Fact]
@ -242,4 +212,80 @@ public class ConsoleTest : BootstrapBlazorTestBase
Assert.Contains("text-danger", cut.Markup);
}
[Fact]
public void FooterTemplate_OK()
{
var cut = Context.RenderComponent<Console>(pb =>
{
pb.Add(a => a.Items, new List<ConsoleMessageItem>()
{
new ConsoleMessageItem() {Message = "Test1", Color = Color.Danger}, new ConsoleMessageItem() {Message = "Test2"}
});
pb.Add(a => a.FooterTemplate, builder =>
{
builder.AddContent(0, "test-footer-template");
});
});
Assert.Contains("test-footer-template", cut.Markup);
}
[Fact]
public void ShowLight_OK()
{
var cut = Context.RenderComponent<Console>(pb =>
{
pb.Add(a => a.Items, new List<ConsoleMessageItem>()
{
new ConsoleMessageItem() {Message = "Test1", Color = Color.Danger}, new ConsoleMessageItem() {Message = "Test2"}
});
pb.Add(a => a.ShowLight, false);
});
Assert.DoesNotContain("light", cut.Markup);
}
[Fact]
public void LightColor_OK()
{
var cut = Context.RenderComponent<Console>(pb =>
{
pb.Add(a => a.Items, new List<ConsoleMessageItem>()
{
new ConsoleMessageItem() {Message = "Test1", Color = Color.Danger}, new ConsoleMessageItem() {Message = "Test2"}
});
pb.Add(a => a.LightColor, Color.Danger);
});
Assert.Contains("light-danger", cut.Markup);
}
[Fact]
public void IsFlashLight_OK()
{
var cut = Context.RenderComponent<Console>(pb =>
{
pb.Add(a => a.Items, new List<ConsoleMessageItem>()
{
new ConsoleMessageItem() {Message = "Test1", Color = Color.Danger}, new ConsoleMessageItem() {Message = "Test2"}
});
pb.Add(a => a.IsFlashLight, false);
});
Assert.DoesNotContain("flash", cut.Markup);
}
[Fact]
public void HeaderTemplate_OK()
{
var cut = Context.RenderComponent<Console>(pb =>
{
pb.Add(a => a.Items, new List<ConsoleMessageItem>()
{
new ConsoleMessageItem() {Message = "Test1", Color = Color.Danger}, new ConsoleMessageItem() {Message = "Test2"}
});
pb.Add(a => a.HeaderTemplate, builder =>
{
builder.AddContent(0, "test-header-template");
});
});
Assert.Contains("test-header-template", cut.Markup);
}
}