!3724 feat(#I65BIW): update DateTimePicker/Range component

* chore: bump version 7.2.0
* test: 精简单元测试
* test: 增加 Range 组件单元测试
* test: 增加 DateTimePicker 单元测试
* refactor: 重构 Range 组件切换日视图逻辑
* refactor: 重构 Range 样式逻辑
* feat: 重构选择框值逻辑
* doc: 精简代码
* doc: 移除 TValue 设置
* doc: 更新空值示例
* refactor: 更新双向绑定示例
* feat: 增加 SelectedValue 初始化值
* doc: 更新 DateTimeOffset 示例
* doc: 更新测试用例
* refactor: 优先处理资源文件逻辑
This commit is contained in:
Argo 2023-01-04 14:29:16 +00:00
parent b0dba7dd3e
commit a2a2fc8bfd
15 changed files with 132 additions and 55 deletions

View File

@ -1,6 +1,6 @@
<div class="row g-3">
<div class="col-sm-6">
<DateTimePicker TValue="DateTime?" Value="@BindValue" ValueChanged="@DateTimeValueChanged" Placement="Placement.Right" />
<DateTimePicker @bind-Value="@BindValue" />
</div>
<div class="col-sm-6">
<input class="form-control" @bind="@BindValueString" />
@ -10,15 +10,9 @@
@code {
private DateTime? BindValue { get; set; } = DateTime.Today;
private Task DateTimeValueChanged(DateTime? d)
{
BindValue = d;
return Task.CompletedTask;
}
private string BindValueString
{
get => BindValue.HasValue ? BindValue.Value.ToString("yyyy-MM-dd") : "";
set => BindValue = DateTime.TryParse(value, out var d) ? d : DateTime.Today;
set => BindValue = DateTime.TryParse(value, out var d) ? d : null;
}
}

View File

@ -1 +0,0 @@
<DateTimePicker TValue="DateTime" Value="@DateTime.Today" />

View File

@ -0,0 +1,5 @@
<DateTimePicker TValue="DateTimeOffset?" Value="Value" />
@code {
private DateTimeOffset? Value { get; set; } = DateTimeOffset.Now;
}

View File

@ -1,6 +1,6 @@
<div class="row g-3">
<div class="col-12 col-sm-8">
<DateTimePicker TValue="DateTime?" @bind-Value="@BindNullValue" />
<DateTimePicker @bind-Value="@BindNullValue" />
</div>
<div class="col-12 col-sm-4">
<Display TValue="string" Value="@GetNullValueString" />

View File

@ -1,6 +1,6 @@
@inject IStringLocalizer<DateTimePickerShowLabel> Localizer
<DateTimePicker TValue="DateTime?" ShowLabel="true" DisplayText="@Localizer["DisplayText"]" @bind-Value="@BindNullValue" />
<DateTimePicker ShowLabel="true" DisplayText="@Localizer["DisplayText"]" @bind-Value="@BindNullValue" />
@code {
private DateTime? BindNullValue { get; set; }

View File

@ -6,28 +6,15 @@
<DateTimePicker @bind-Value="@Value" />
</div>
<div class="col-12 col-sm-auto align-self-end">
<Button ButtonType="ButtonType.Submit" Text="@SubmitText" Icon="fa-solid fa-floppy-disk" />
<Button ButtonType="ButtonType.Submit" Text="@Localizer["SubmitText"]" Icon="fa-solid fa-floppy-disk" />
</div>
</div>
</ValidateForm>
@code {
/// <summary>
/// SubmitText
/// </summary>
private string? SubmitText { get; set; }
/// <summary>
/// ModelValidateValue
/// </summary>
[Required]
public DateTime? Value { get; set; }
/// <summary>
/// OnInitialized 方法
/// </summary>
protected override void OnInitialized()
{
SubmitText ??= Localizer[nameof(SubmitText)];
}
}

View File

@ -2559,10 +2559,10 @@
"TimeIntro": "Click the confirm button to select the box value consistent with the text box value",
"ValidateFormTitle": "Client validation",
"ValidateFormIntro": "Check data validity and prompt automatically based on custom validation rules",
"DateTitle": "Click on the pop-up date box",
"DateIntro": "Select the control based on the date of the day in 「day」 as the base unit",
"PlacementTitle": "Data is bound in both directions",
"PlacementIntro": "The values in the text box change as the date component time changes",
"DateTimeOffsetTitle": "Click on the pop-up date box",
"DateTimeOffsetIntro": "Select the control based on the date of the day in 「day」 as the base unit",
"BindValueTitle": "Data is bound in both directions",
"BindValueIntro": "The values in the text box change as the date component time changes",
"ViewModeTitle": "Selector with time",
"ViewModeIntro": "Select the date and time in the same selector, click the confirm button and close the pop-up window",
"ViewModeTip": "Set the value of the <code>viewMode</code> property to The <code>DateTime</code> of DatePickerViewMode.DateTime",

View File

@ -2566,10 +2566,10 @@
"TimeIntro": "点击确认按钮时间选择框值与文本框值一致",
"ValidateFormTitle": "客户端验证",
"ValidateFormIntro": "根据自定义验证规则进行数据有效性检查并自动提示",
"DateTitle": "点击弹出日期框",
"DateIntro": "以「日」为基本单位,基础的日期选择控件",
"PlacementTitle": "数据双向绑定",
"PlacementIntro": "日期组件时间改变时文本框内的数值也跟着改变",
"DateTimeOffsetTitle": "点击弹出日期框",
"DateTimeOffsetIntro": "以「日」为基本单位,基础的日期选择控件,此示例绑定 <code>DateTimeOffset</code> 数据类型",
"BindValueTitle": "数据双向绑定",
"BindValueIntro": "日期组件时间改变时文本框内的数值也跟着改变",
"ViewModeTitle": "带时间的选择器",
"ViewModeIntro": "在同一个选择器里选择日期和时间,点击确认按钮后关闭弹窗",
"ViewModeTip": "设置 <code>ViewMode</code> 属性值为 <code>DatePickerViewMode.DateTime</code>",

View File

@ -12,9 +12,9 @@
<DemoBlock Title="@Localizer["ValidateFormTitle"]" Introduction="@Localizer["ValidateFormIntro"]" Name="ValidateForm" Demo="typeof(Demos.DateTimePicker.DateTimePickerValidateDemo)" />
<DemoBlock Title="@Localizer["DateTitle"]" Introduction="@Localizer["DateIntro"]" Name="Date" Demo="typeof(Demos.DateTimePicker.DateTimePickerDate)" />
<DemoBlock Title="@Localizer["DateTimeOffsetTitle"]" Introduction="@Localizer["DateTimeOffsetIntro"]" Name="DateTimeOffset" Demo="typeof(Demos.DateTimePicker.DateTimePickerDateTimeOffsetDemo)" />
<DemoBlock Title="@Localizer["PlacementTitle"]" Introduction="@Localizer["PlacementIntro"]" Name="Placement" Demo="typeof(Demos.DateTimePicker.DateTimePickerPlacement)" />
<DemoBlock Title="@Localizer["BindValueTitle"]" Introduction="@Localizer["BindValueIntro"]" Name="BindValue" Demo="typeof(Demos.DateTimePicker.DateTimePickerBindValueDemo)" />
<DemoBlock Title="@Localizer["ViewModeTitle"]" Introduction="@Localizer["ViewModeIntro"]" Name="ViewMode" Demo="typeof(Demos.DateTimePicker.DateTimePickerViewMode)">
<p>@((MarkupString)Localizer["ViewModeTip"].Value)</p>

View File

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

View File

@ -57,9 +57,7 @@ public partial class DatePickerBody
.AddClass("current", day == Value && Ranger == null && day.Month == CurrentDate.Month && !overflow)
.AddClass("start", Ranger != null && day == Ranger.SelectedValue.Start.Date)
.AddClass("end", Ranger != null && day == Ranger.SelectedValue.End.Date)
.AddClass("range", Ranger != null && CurrentDate.Month >= Ranger.SelectedValue.Start.Month
&& Ranger.SelectedValue.Start != DateTime.MinValue && Ranger.SelectedValue.End != DateTime.MinValue
&& day >= Ranger.SelectedValue.Start && day <= Ranger.SelectedValue.End)
.AddClass("range", Ranger != null && day >= Ranger.SelectedValue.Start && day <= Ranger.SelectedValue.End)
.AddClass("today", day == DateTime.Today)
.AddClass("disabled", IsDisabled(day) || overflow)
.Build();

View File

@ -137,6 +137,13 @@ public partial class DateTimePicker<TValue>
{
base.OnParametersSet();
DateTimePlaceHolderText ??= Localizer[nameof(DateTimePlaceHolderText)];
DatePlaceHolderText ??= Localizer[nameof(DatePlaceHolderText)];
GenericTypeErroMessage ??= Localizer[nameof(GenericTypeErroMessage)];
DateTimeFormat ??= Localizer[nameof(DateTimeFormat)];
DateFormat ??= Localizer[nameof(DateFormat)];
Icon ??= "fa-regular fa-calendar-days";
var type = typeof(TValue);
// 判断泛型类型
@ -145,13 +152,6 @@ public partial class DateTimePicker<TValue>
throw new InvalidOperationException(GenericTypeErroMessage);
}
DateTimePlaceHolderText ??= Localizer[nameof(DateTimePlaceHolderText)];
DatePlaceHolderText ??= Localizer[nameof(DatePlaceHolderText)];
GenericTypeErroMessage ??= Localizer[nameof(GenericTypeErroMessage)];
DateTimeFormat ??= Localizer[nameof(DateTimeFormat)];
DateFormat ??= Localizer[nameof(DateFormat)];
Icon ??= "fa-regular fa-calendar-days";
// 泛型设置为可为空
AllowNull = Nullable.GetUnderlyingType(type) != null;
@ -167,13 +167,25 @@ public partial class DateTimePicker<TValue>
}
// Value 为 MinValue 时 设置 Value 默认值
if (AutoToday)
if (AutoToday && (Value == null || Value.ToString() == DateTime.MinValue.ToString()))
{
if (Value == null || Value.ToString() == DateTime.MinValue.ToString())
SelectedValue = DateTime.Today;
if (!AllowNull)
{
SelectedValue = DateTime.Today;
CurrentValueAsString = SelectedValue.ToString("yyyy-MM-dd HH:mm:ss");
}
}
else if (Value is DateTime dt)
{
SelectedValue = dt;
}
else
{
var offset = (DateTimeOffset?)(object)Value;
SelectedValue = offset.HasValue
? offset.Value.DateTime
: DateTime.MinValue;
}
}
/// <summary>

View File

@ -324,7 +324,7 @@ public partial class DateTimeRange
/// <param name="d"></param>
internal void UpdateStart(DateTime d)
{
StartValue = StartValue.AddYears(d.Year - StartValue.Year).AddMonths(d.Month - StartValue.Month);
StartValue = d;
EndValue = StartValue.AddMonths(1);
StateHasChanged();
}
@ -335,7 +335,7 @@ public partial class DateTimeRange
/// <param name="d"></param>
internal void UpdateEnd(DateTime d)
{
EndValue = EndValue.AddYears(d.Year - EndValue.Year).AddMonths(d.Month - EndValue.Month);
EndValue = d;
StartValue = EndValue.AddMonths(-1);
StateHasChanged();
}
@ -364,11 +364,12 @@ public partial class DateTimeRange
SelectedValue.End = DateTime.MinValue;
}
if (d.Year < StartValue.Year || d.Month < StartValue.Month)
var startDate = StartValue.AddDays(1 - StartValue.Day);
if (d < startDate)
{
UpdateStart(d);
}
else if (d.Month > EndValue.Month)
else if (d > startDate.AddMonths(2).AddDays(-1))
{
UpdateEnd(d);
}

View File

@ -17,7 +17,61 @@ public class DateTimePickerTest : BootstrapBlazorTestBase
pb.Add(a => a.Value, DateTime.MinValue);
});
// 设置 Value 为 MinValue 内部更改为 DateTime.Now
Assert.NotEqual(DateTime.MinValue, cut.Instance.Value);
Assert.Equal(DateTime.MinValue, cut.Instance.Value);
}
[Fact]
public void AutoToday_Ok()
{
var cut = Context.RenderComponent<DateTimePicker<DateTime>>(pb =>
{
pb.Add(a => a.Value, DateTime.MinValue);
});
Assert.Equal(DateTime.Today, cut.Instance.Value);
cut.SetParametersAndRender(pb =>
{
pb.Add(a => a.AutoToday, false);
pb.Add(a => a.Value, DateTime.MinValue);
});
Assert.Equal(DateTime.MinValue, cut.Instance.Value);
}
[Fact]
public void AllowNull_Ok()
{
var cut = Context.RenderComponent<DateTimePicker<DateTime?>>(pb =>
{
pb.Add(a => a.Value, DateTime.MinValue);
});
Assert.Equal(DateTime.MinValue, cut.Instance.Value);
}
[Fact]
public void DataTimeOffsetNull_Ok()
{
var cut = Context.RenderComponent<DateTimePicker<DateTimeOffset?>>(pb =>
{
pb.Add(a => a.Value, DateTimeOffset.MinValue);
});
Assert.Equal(DateTimeOffset.MinValue, cut.Instance.Value);
cut.SetParametersAndRender(pb =>
{
pb.Add(a => a.Value, null);
pb.Add(a => a.AutoToday, false);
});
Assert.Null(cut.Instance.Value);
}
[Fact]
public void DataTimeOffset_Ok()
{
var cut = Context.RenderComponent<DateTimePicker<DateTimeOffset>>(pb =>
{
pb.Add(a => a.Value, DateTimeOffset.MinValue);
});
Assert.Equal(DateTimeOffset.MinValue, cut.Instance.Value);
}
[Fact]
@ -105,6 +159,13 @@ public class DateTimePickerTest : BootstrapBlazorTestBase
cut.InvokeAsync(() => buttons[0].Click());
Assert.Null(cut.Instance.Value);
cut.SetParametersAndRender(pb =>
{
pb.Add(a => a.AutoToday, false);
});
cut.InvokeAsync(() => buttons[0].Click());
Assert.Null(cut.Instance.Value);
}
[Fact]
@ -753,7 +814,6 @@ public class DateTimePickerTest : BootstrapBlazorTestBase
Assert.True(confirm);
}
[JSModuleNotInherited]
class MockDateTimePicker : DatePickerBody
{
public static bool GetSafeYearDateTime_Ok()

View File

@ -2,6 +2,7 @@
// 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/
using AngleSharp.Dom;
using System.ComponentModel.DataAnnotations;
namespace UnitTest.Components;
@ -30,6 +31,26 @@ public class DateTimeRangeTest : BootstrapBlazorTestBase
// 内部 StartValue 自动减一个月
}
[Fact]
public void RangeValue_Ok()
{
var cut = Context.RenderComponent<DateTimeRange>();
var cells = cut.FindAll(".date-table tbody span");
var end = cells.First(i => i.TextContent == "7");
var first = cells.First(i => i.TextContent == "1");
cut.InvokeAsync(() => end.Click());
cut.InvokeAsync(() => first.Click());
// confirm
var confirm = cut.FindAll(".is-confirm").Last();
cut.InvokeAsync(() => confirm.Click());
var value = cut.Instance.Value;
var startDate = DateTime.Today.AddDays(1 - DateTime.Today.Day);
var endDate = startDate.AddDays(7).AddSeconds(-1);
Assert.Equal(startDate, value.Start);
Assert.Equal(endDate, value.End);
}
[Fact]
public void TodayButtonText_Ok()
{