diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ee68c3d9..f37d3d57 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: .NET +name: 编译 & 测试 on: push: @@ -13,11 +13,13 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Setup .NET + - name: 配置 .NET uses: actions/setup-dotnet@v2 with: dotnet-version: 6.0.x - - name: Restore dependencies + - name: 还原包 run: dotnet restore - - name: Build + - name: 编译 run: dotnet build --no-restore + - name: 测试 + run: dotnet test diff --git a/doc/TDesignBlazor.Docs.Shared/Components/Example.razor b/doc/TDesignBlazor.Docs.Shared/Components/Example.razor index 3393fe51..c3f706e2 100644 --- a/doc/TDesignBlazor.Docs.Shared/Components/Example.razor +++ b/doc/TDesignBlazor.Docs.Shared/Components/Example.razor @@ -3,7 +3,7 @@

@Title

@if (Description is not null) { -

@Description

+

@Description

} @* @@ -18,11 +18,11 @@
- @RunContent + @RunContent
@@ -37,11 +37,11 @@ [Parameter] public RenderFragment Description { get; set; } [Parameter] public RenderFragment CodeContent { get; set; } [Parameter] public RenderFragment RunContent { get; set; } - [Parameter]public string? RunStyle{ get; set; } + [Parameter] public string? RunStyle { get; set; } bool Active = false; - string GetStyle() =>Active?"": "display:none"; + string GetStyle() => Active ? "" : "display:none"; string CodeClass => HtmlHelper.CreateCssBuilder().Append("content code") //.Append(Active, "code-collapse", "code-expand") .ToString() @@ -55,3 +55,4 @@ StateHasChanged(); } } + diff --git a/doc/TDesignBlazor.Docs.Shared/Layouts/MainLayout.razor b/doc/TDesignBlazor.Docs.Shared/Layouts/MainLayout.razor index c9d7eb20..4f51fba6 100644 --- a/doc/TDesignBlazor.Docs.Shared/Layouts/MainLayout.razor +++ b/doc/TDesignBlazor.Docs.Shared/Layouts/MainLayout.razor @@ -6,7 +6,7 @@ TDesignBlazor Logo    -

基于 TDesignBlazor 的 Blazor 企业级组件库

+

基于 TDesign 的 Blazor 企业级组件库

@@ -20,7 +20,7 @@
- +
diff --git a/doc/TDesignBlazor.Docs.Shared/Layouts/NavMenu.razor b/doc/TDesignBlazor.Docs.Shared/Layouts/NavMenu.razor index 96370825..98c3822d 100644 --- a/doc/TDesignBlazor.Docs.Shared/Layouts/NavMenu.razor +++ b/doc/TDesignBlazor.Docs.Shared/Layouts/NavMenu.razor @@ -37,6 +37,7 @@ Loading 加载 Table 表格 Tag 标签 + Progress 进度条 Image 图片 diff --git a/doc/TDesignBlazor.Docs.Shared/Pages/Components/ProgressPage.razor b/doc/TDesignBlazor.Docs.Shared/Pages/Components/ProgressPage.razor new file mode 100644 index 00000000..ad4312df --- /dev/null +++ b/doc/TDesignBlazor.Docs.Shared/Pages/Components/ProgressPage.razor @@ -0,0 +1,154 @@ +@page "/components/progress" + +展示操作的当前进度。 + + + + 以线形表示进度的组件,可以选择性地配有文字或图标补充显示进度和状态。多用于信息量较为丰富的情况。 + +

默认在线形外展示进度和状态

+ + + 默认样式 + + + + 100% + + + + 进度完成 + + + + 进度状态发生重大错误 + + + + 进度被中断 + + + +

可以在线形内展示进度信息

+ + + 默认样式 + + + + 进度0-10%时数字位置出现在目前进度的右边区域 + + + +
+ + @Code.Create(@" +```html +

默认在线形外展示进度和状态

+默认样式 + + +100% + + +进度完成 + + +进度状态发生重大错误 + + +进度被中断 + + + +

可以在线形内展示进度信息

+默认样式 + + +进度0-10%时数字位置出现在目前进度的右边区域 + +``` +") +
+
+ + 以线形表示进度的组件,环内可选择性地配有文字或图标补充显示进度和状态。多用于需要强调进度百分比的情况。 + + + + 默认样式 + + + + 不显示数字 + + + + 自定义内容 + + + +

+ + + 进度完成 + + + + 进度状态发生错误 + + + + 进度被中断 + + +

+ + + 小尺寸 + + + + 默认尺寸 + + + + 大尺寸 + + + +
+ + @Code.Create(@" +```html +默认样式 + + +不显示数字 + + +自定义内容 + + +进度完成 + + +进度状态发生错误 + + +进度被中断 + + +小尺寸 + + +默认尺寸 + + +大尺寸 + + +``` +") + +
\ No newline at end of file diff --git a/src/TDesignBlazor.Test/Components/ProgressTest.cs b/src/TDesignBlazor.Test/Components/ProgressTest.cs new file mode 100644 index 00000000..86cc852d --- /dev/null +++ b/src/TDesignBlazor.Test/Components/ProgressTest.cs @@ -0,0 +1,98 @@ +using ComponentBuilder; + +namespace TDesignBlazor.Test.Components; +public class ProgressTest : TestBase +{ + [Fact(DisplayName = "进度条 - 渲染 50% 进度的进度条和样式")] + public void Test_Progress_Default() + { + GetComponent(m => m.Add(p => p.Value, 50)).MarkupMatches(@" +
+
+
+
+
+
+
+
+"); + } + + [Fact(DisplayName = "进度条 - ShowLabel 参数")] + public void Test_Progress_ShowLabel_Parameter() + { + GetComponent(m => m.Add(p => p.Value, 50).Add(p => p.ShowLabel, true)).MarkupMatches(@" +
+
+
+
+
+
50%
+
+
+"); + } + + [Fact(DisplayName = "进度条 - Plump 类型时,Value < 10 的样式")] + public void Test_Progress_Plump_UnderTen_Label() + { + GetComponent(m => m.Add(p => p.Value, 5).Add(p => p.ShowLabel, true).Add(p => p.Theme, ProgressTheme.Plump)).MarkupMatches(@" +
+
+
+
5%
+
+
+"); + } + + [Fact(DisplayName = "进度条 - Plump 类型时,Value >= 10 的样式")] + public void Test_Progress_Plump_OverTen_Label() + { + GetComponent(m => m.Add(p => p.Value, 15).Add(p => p.ShowLabel, true).Add(p => p.Theme, ProgressTheme.Plump)).MarkupMatches(@" +
+
+
+
15%
+
+
+
+"); + } + + [Theory(DisplayName ="进度条 - Status 参数")] + [InlineData(new object[] { Status.Warning })] + [InlineData(new object[] { Status.Success })] + [InlineData(new object[] { Status.Error })] + [InlineData(new object[] { Status.Default })] + public void Test_Progress_Status_Parameter(Status status) + { + GetComponent(m => m.Add(p => p.Value, 15).Add(p => p.ShowLabel, true).Add(p => p.Status, status)).MarkupMatches($@" +
+
+
+
+
+
15%
+
+
+"); + } + + [Fact(DisplayName ="进度条 - Circle 类型")] + public void Test_Progress_Circle() + { + GetComponent(m => m.Add(p => p.Value, 30).Add(p => p.ShowLabel, true).Add(p => p.Theme, ProgressTheme.Circle).Add(p=>p.Size,Size.Small)) + .MarkupMatches($@" +
+
+
30%
+ + + + +
+
+"); + } +} diff --git a/src/TDesignBlazor/Components/Progress.cs b/src/TDesignBlazor/Components/Progress.cs new file mode 100644 index 00000000..b5d91547 --- /dev/null +++ b/src/TDesignBlazor/Components/Progress.cs @@ -0,0 +1,283 @@ +using System.Drawing; +using System.Linq.Expressions; +using Microsoft.AspNetCore.Components.Rendering; + +namespace TDesignBlazor; + +/// +/// 展示操作的当前进度的进度条。 +/// +[CssClass("t-progress")] +public class Progress : BlazorComponentBase,IHasActive +{ + /// + /// 设置是否显示进度条的百分比。true 则显示 的百分比,否则,根据状态显示对应的图标。 + /// + [Parameter] public bool ShowLabel { get; set; } + + /// + /// 设置是否隐藏进度条的百分比。 + /// true 则不显示百分比和状态图标,即使 已设置。 + /// + [Parameter]public bool HideLabel { get; set; } + /// + /// 设置进度条的风格。 + /// + [Parameter] public ProgressTheme Theme { get; set; } = ProgressTheme.Line; + + /// + /// 设置进度条的状态。 + /// + [Parameter] public Status? Status { get; set; } + /// + /// 设置进度条长度的百分比。 + /// + [Parameter] public double Value { get; set; } + /// + /// 自定义显示标签的内容。否则显示 的百分比。 + /// + [Parameter]public string? Label { get; set; } + /// + /// 设置进度条具备渐变效果。 + /// + [Parameter] public bool Active { get; set; } + + /// + /// 进度条的大小。 + /// + [Parameter] public Size Size { get; set; } = Size.Medium; + + /// + protected override void OnParametersSet() + { + base.OnParametersSet(); + if (Value < 0 || Value > 100) + { + throw new ArgumentException($"{nameof(Value)} 的值必须在 0-100 之间,当前的值是 {Value}。"); + } + } + + /// + protected override void AddContent(RenderTreeBuilder builder, int sequence) + { + switch (Theme) + { + case ProgressTheme.Line: + BuildLine(builder); + break; + case ProgressTheme.Plump: + BuildPlump(builder); + break; + case ProgressTheme.Circle: + BuildCircle(builder); + break; + } + } + + /// + /// 根据 获取 style 的宽高。 + /// + private (int? size, int? fontSize) GetSizeStyle() + => Size switch + { + TDesignBlazor.Size.Small => (72, 14), + TDesignBlazor.Size.Medium => (112, 20), + TDesignBlazor.Size.Large => (160, 36), + _ => (default, default), + }; + + /// + /// 构造 的 HTML 代码。 + /// + private void BuildLine(RenderTreeBuilder builder) + { + builder.CreateDiv(0, (RenderFragment)(content => + { + content.CreateElement(0, "div", bar => + { + BuildProgressInner(bar); + }, new { @class = "t-progress__bar" }); + + BuildProgressInfo(content, 1); + }), + new + { + @class = HtmlHelper.CreateCssBuilder() + .Append("t-progress--thin") + .Append(Status is null, "t-progress--status--undefined", $"t-progress--status--{Status?.GetCssClass()}") + .Append("t-progress--status--active", Active) + }); + } + + /// + /// 构造 的 HTML 代码。 + /// + private void BuildPlump(RenderTreeBuilder builder) + { + builder.CreateElement(0, "div", bar => + { + if (Value > 10) + { + BuildProgressInner(bar,0, info => + { + BuildProgressInfo(info, 0); + }); + } + else + { + BuildProgressInner(bar); + BuildProgressInfo(bar, 1); + } + }, new + { + @class = HtmlHelper.CreateCssBuilder() + .Append("t-progress__bar") + .Append("t-progress--plump") + .Append(Status is null, "t-progress--status--undefined", $"t-progress--status--{Status?.GetCssClass()}") + .Append("t-progress--status--active", Active) + .Append(Value <= 10, "t-progress--under-ten", "t-progress--over-ten") + }); + } + + private void BuildCircle(RenderTreeBuilder builder) + { + (int? size, int? fontSize) = GetSizeStyle(); + builder.CreateElement(0, "div", circle => + { + BuildProgressInfo(circle,0,true); + circle.CreateElement(1, "svg", svg => + { + var param = GetCircleParameter(); + var array = GetDashData(param.r); + var size = GetSizeStyle(); + svg.CreateElement(0, "circle", attributes: new + { + param.cx, + param.cy, + param.r, + param.stroke_width, + fill="none", + @class= "t-progress__circle-outer" + }); + + svg.CreateElement(0, "circle", attributes: new + { + param.cx, + param.cy, + param.r, + param.stroke_width, + fill = "none", + stroke_linecap="round", + transform=$"matrix(0,-1,1,0,0,{size.size})", + stroke_dasharray=$"{array.d1} {array.d2}", + @class = "t-progress__circle-inner" + }); + }, new + { + width=size, + height=size, + viewBox=string.Format("0 0 {0} {0}",size) + }); + }, new + { + @class = HtmlHelper.CreateCssBuilder() + .Append("t-progress--circle") + .Append(Status is null, "t-progress--status--undefined", $"t-progress--status--{Status?.GetCssClass()}") + .Append("t-progress--status--active", Active), + style=HtmlHelper.CreateStyleBuilder() + .Append($"width:{size}px",size.HasValue) + .Append($"height:{size}px", size.HasValue) + .Append($"font-size:{fontSize}px",fontSize.HasValue) + }); + + (int cx, int cy, int r,int stroke_width) GetCircleParameter() + => Size switch + { + Size.Small => (36, 36, 34,4), + Size.Medium => (56, 56, 53,6), + Size.Large => (80, 80, 77,6) + }; + + // 返回 stroke_dasharray 属性 + (double d1,double d2) GetDashData(double radius) + { + var circlePerimeter = 2 * Math.PI * radius; + + return (circlePerimeter * (Value / 100D), circlePerimeter + 1); + } + } + + /// + /// 构建 <div class="t-progress__inner">xx</div> 这段代码 + /// + /// + /// + /// + /// + private void BuildProgressInner( RenderTreeBuilder content, int sequence=0, RenderFragment? childContent = default, string? additionalStyle = default) + { + content.CreateElement(sequence, "div", childContent, + new + { + @class = "t-progress__inner", + style = HtmlHelper.CreateStyleBuilder().Append($"width:{Value}%").Append(additionalStyle ??= string.Empty), + }); + } + + /// + /// 构建 <div class="t-progress__info">xx</div> 这段代码 + /// + private void BuildProgressInfo(RenderTreeBuilder builder, int sequence,bool circle=false) + { + builder.CreateElement(sequence, "div", content => + { + if (HideLabel) + { + return; + } + + if (ShowLabel) + { + content.AddContent(0, $"{GetLabel()}"); + } + else if (Status is not null) + { + content.CreateComponent(0, attributes: new + { + Name = Status?.GetStatusIconName(!circle?default: s => s switch + { + TDesignBlazor.Status.Success => IconName.Check, + TDesignBlazor.Status.Error => IconName.Close, + TDesignBlazor.Status.Warning => IconName.Error, + _ => default + }), + AdditionalCssClass = "t-progress__icon" + }); + } + }, new { @class = "t-progress__info" }); + + string? GetLabel() + { + return Label ?? $"{Value}%"; + } + } +} + +/// +/// 进度条的类型。 +/// +public enum ProgressTheme +{ + /// + /// 瘦小的线,很细的线。 + /// + Line, + /// + /// 丰满的线,比较粗的线。 + /// + Plump, + /// + /// 圆形。像仪表盘一样。 + /// + Circle +} \ No newline at end of file diff --git a/src/TDesignBlazor/TDesignBlazor.csproj b/src/TDesignBlazor/TDesignBlazor.csproj index a3406338..b757df04 100644 --- a/src/TDesignBlazor/TDesignBlazor.csproj +++ b/src/TDesignBlazor/TDesignBlazor.csproj @@ -1,4 +1,4 @@ - + 10 @@ -59,10 +59,9 @@ - - - - + + + diff --git a/src/TDesignBlazor/TDesignBlazorExtensions.cs b/src/TDesignBlazor/TDesignBlazorExtensions.cs index 7ee4e59d..781b6500 100644 --- a/src/TDesignBlazor/TDesignBlazorExtensions.cs +++ b/src/TDesignBlazor/TDesignBlazorExtensions.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Runtime.CompilerServices; namespace TDesignBlazor; /// @@ -18,4 +19,26 @@ public static bool TryGetCustomAttribute(this MemberInfo memberInfo, attribute = memberInfo.GetCustomAttribute(); return attribute != null; } + + /// + /// 获取状态对应的图标名称。 + /// + /// The status. + /// 状态和图标的映射关系。 + /// 图标枚举。 + public static IconName? GetStatusIconName(this Status status, Func? mapping = default) + { + if (mapping is null) + { + return status switch + { + Status.Default => IconName.InfoCircle, + Status.Success => IconName.CheckCircle, + Status.Error => IconName.CloseCircle, + Status.Warning => IconName.ErrorCircle, + _ => default + }; + } + return mapping(status); + } } diff --git a/src/TDesignBlazor/_Imports.cs b/src/TDesignBlazor/_Imports.cs index 92ed971c..541a27eb 100644 --- a/src/TDesignBlazor/_Imports.cs +++ b/src/TDesignBlazor/_Imports.cs @@ -2,4 +2,6 @@ global using ComponentBuilder.Parameters; global using Microsoft.AspNetCore.Components; -global using Microsoft.AspNetCore.Components.Web; \ No newline at end of file +global using Microsoft.AspNetCore.Components.Web; + +global using OneOf; diff --git a/src/TDesignBlazor/wwwroot/images/favicon.ico b/src/TDesignBlazor/wwwroot/images/favicon.ico new file mode 100644 index 00000000..086ac804 Binary files /dev/null and b/src/TDesignBlazor/wwwroot/images/favicon.ico differ diff --git a/src/TDesignBlazor/wwwroot/images/favicon.png b/src/TDesignBlazor/wwwroot/images/favicon.png new file mode 100644 index 00000000..d72738d0 Binary files /dev/null and b/src/TDesignBlazor/wwwroot/images/favicon.png differ